Специализация шаблона для члена шаблона класса шаблона
Это, вероятно, только проблема синтаксиса.
Итак, у меня есть этот класс шаблона:
template <typename String, template<class> class Allocator>
class basic_data_object
{
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
};
И еще один:
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
{
};
Теперь я хочу специализировать второй параметр T
с первым внутренним typedef array_container
для любого заданного типа.
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
typename basic_data_object<String, Allocator>::template array_container<T>>
{
};
Но эта специализация, похоже, не сопоставляется, когда я передаю std::vector в качестве последнего параметра.
Если я создаю временный жесткий код typedef:
typedef basic_data_object<std::string, std::allocator<std::string>> data_object;
И используйте его для специализации, все работает:
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
data_object::template array_container<T>>
{
};
Чего я пропустил?:)
Альтернативно, какой лучший (самый маленький/самый чистый) способ сделать эту работу?
Ответы
Ответ 1
Ответ Джонатана Вакели дает причину, почему ваш код не работает.
Мой ответ показывает, как решить проблему.
В вашем примере тип контейнера, по которому вы хотите специализироваться, определяется вне basic_data_object
, поэтому вы можете, конечно, использовать его непосредственно в своей специализации:
template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,std::vector<T,A>>
{ };
Это определенно соответствует стандарту и работает со всеми компиляторами.
В случае, когда тип определен в basic_data_object
, вы можете вывести его из класса.
Пример: Вместо
template<typename S, template<class> class A>
struct a_data_object
{
template<typename T>
struct a_container
{ };
};
напишите это:
template<typename S, template<class> class A, typename T>
// you can perhaps drop S and A if not needed...
struct a_container
{ };
template<typename S, template<class> class A, typename T>
struct a_data_object
{
// use a_container<S,A,T>
};
Теперь вы можете специализироваться на:
template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,a_container<S,A,T>>
{ };
Примечание. Следующее "решение", по-видимому, является ошибкой с GCC 4.8.1.
Если контейнер определен только в закрывающем шаблоне и не может быть удален, вы можете сделать это:
-
Получить тип контейнера из basic_data_object
:
template<typename S, template<class> class A, typename T>
using bdo_container = basic_data_object<S,A>::array_container<T>;
-
Напишите специализацию для этого типа:
template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,bdo_container<S,A,T>>
{ };
Ответ 2
В стандарте С++ в пункте [temp.class.spec.match] говорится:
Частичная специализация соответствует данному фактическому шаблону список аргументов, если аргументы шаблона частичного специализация может быть выведена из фактического шаблона список аргументов (14.8.2).
14.8.2 - [temp.arg.deduct], то есть предложение, описывающее вывод аргумента шаблона для шаблонов функций.
Если вы измените свой код на использование аналогичного шаблона функции и попытаетесь вызвать его, вы увидите, что аргументы не могут быть выведены:
template <typename String, typename T>
void deduction_test(String,
typename basic_data_object<String, std::allocator>::template array_container<T>)
{ }
int main()
{
deduction_test(std::string{}, std::vector<int, std::allocator<int>>{});
}
(я удалил параметр Allocator
, так как нет способа передать шаблонный шаблон в качестве аргумента функции, а в типе basic_data_object
это не выводимый контекст, я не верю, что он влияет на результат. )
Оба clang и GCC говорят, что они не могут вывести T
здесь. Поэтому частичная специализация не будет соответствовать тем же типам, что и аргументы шаблона.
Итак, я еще не ответил на вопрос, пояснил только, что причина в правилах вывода аргумента шаблона и показана эквивалентность с вычитанием в шаблонах функций.
В 14.8.2.5 [temp.deduct.type] мы получаем список не выводимых контекстов, которые предотвращают дедукцию, и следующее правило в пункте 6:
Если имя типа указано таким образом, который включает невыводимый контекст, все типы, которые содержат это имя типа, также не выведены.
Так как basic_data_object<String, Allocator>
находится в не выводимом контексте (это спецификатор вложенного имени, т.е. появляется перед ::
), что означает, что тип T
также не выводится, что является именно тем, что Clang и GCC сообщите нам.
С вашим временным hardcoded typedef не существует контекста без вывода, поэтому вывод для T
выполняется с использованием шаблона функции deduction_test
:
template <typename String, typename T>
void deduction_test(String,
typename data_object::template array_container<T>)
{ }
int main()
{
deduction_test(std::string{}, std::vector<int, std::allocator<int>>{}); // OK
}
Итак, соответственно, ваша частная специализация шаблона шаблона может быть сопоставлена при использовании этого типа.
Я не вижу способа заставить его работать, не изменяя определение get_data_object_value
, но если это опция, вы можете удалить необходимость вывести тип array_container
и вместо этого использовать признак, чтобы определить, является ли тип это тот тип, который вы хотите, и специализируетесь на результате этого признака:
#include <string>
#include <vector>
#include <iostream>
template <typename String, template<class> class Allocator>
class basic_data_object
{
public:
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
template<typename T>
struct is_ac : std::false_type { };
template<typename T>
struct is_ac<array_container<T>> : std::true_type { };
};
template <typename String, template<class> class Allocator, typename T, bool = basic_data_object<String, Allocator>::template is_ac<T>::value>
struct get_data_object_value
{
};
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value<String, Allocator, T, true>
{
void f() { }
};
int main()
{
get_data_object_value<std::string,std::allocator,std::vector<short>> obj;
obj.f();
}
Это не очень масштабируется, если вам нужны несколько специализированных специализированных шаблонов шаблонов, поскольку вам нужно добавить несколько параметров шаблона bool
с аргументами по умолчанию.
Ответ 3
По какой-то причине проблема, похоже, связана с двойным уровнем шаблонов. Я оставлю вас проверить 3 тестовых примера ниже, они просты:
- Удалить аргументы шаблона
First
: работает как ожидалось
- Сделайте
First
шаблон, но внутренний тип простой: работает как ожидалось
- Сделайте как
First
, так и внутренние шаблоны типов: компилируется, но вывод неожиданно
Примечание: параметр шаблона шаблона Allocator
бесполезен, чтобы воспроизвести проблему, поэтому я ее оставил.
Примечание: как GCC (идеонная версия, 4.8.1, я считаю), так и Clang (версия Coliru, 3.4) компилируют код и все же дают тот же самый непонятный результат
Из приведенных выше примеров я выводил:
-
, что это НЕ НЕИСПРАВНОСТЬ. иначе зачем (2) работать?
-
, что это НЕ проблема с псевдонимом; иначе зачем (1) работать?
И, следовательно, проблема в том, что проблема в том, что текущие подсказки заставили нас поверить или что у gcc и Clang есть ошибка.
EDIT: благодаря Джонатану Вакели, который терпеливо обучил меня достаточно, чтобы я мог наконец понять как стандартную формулировку, связанную с этим делом, так и ее применение. Теперь я попытаюсь объяснить это (снова) своими словами. Пожалуйста, обратитесь к ответу Джонатана за точные стандартные кавычки (все это находится в [temp.deduct.type])
- При выводе параметров шаблона (P i), будь то для функций или классов, вывод выполняется независимо для каждого аргумента.
- Каждый аргумент должен предоставить нулевой или один кандидат C i для каждого параметра; если аргумент предоставит более одного кандидата, он не предоставляет ничего.
- Таким образом, каждый аргумент создает словарь D n: P i → C i, который отображает подмножество (возможно, пустое) из параметры шаблона, которые должны быть выведены их кандидату.
- Словари D n объединяются вместе, параметр по параметру:
- если только один словарь имеет кандидата для данного параметра, то этот параметр принимается с этим кандидатом
- если несколько словарей имеют одинаковый кандидат для данного параметра, то этот параметр принимается с этим кандидатом
- если несколько словарей имеют разные несовместимые (*) кандидаты для данного параметра, то этот параметр отклоняется
- Если окончательный словарь завершен (сопоставляет каждый параметр с принятым кандидатом), то вывод удался, иначе он терпит неудачу.
(*), похоже, существует возможность найти "общий тип" у доступных кандидатов... но здесь это не имеет никакого значения.
Теперь мы можем применить это к предыдущим примерам:
1) Существует единственный параметр шаблона T
:
- соответствие шаблону
std::vector<int>
против typename First::template ArrayType<T>
(которое std::vector<T>
), получаем D 0: { T -> int }
- объединение единственного словаря дает
{ T -> int }
, поэтому T
выводится как int
2) Существует единственный параметр шаблона String
- соответствие шаблону
std::vector<int>
против String
, получаем D 0: { String -> std::vector<int> }
- соответствие шаблону
std::vector<int>
против typename First<String>::ArrayType
мы попали в не выводимый контекст (многие значения String
могут поместиться), получаем D 1: {}
- слияние двух словарей дает
{ String -> std::vector<int> }
, поэтому String
выводится std::vector<int>
3) Два параметра шаблона String
и T
существуют
- соответствие шаблону
std::vector<char>
против String
, получаем D 0: { String -> std::vector<char> }
- соответствие шаблону
std::vector<int>
против typename First<String>::template ArrayType<T>
мы попали в не выводимый контекст, получим D 1: {}
- слияние двух словарей дает
{ String -> std::vector<char> }
, что является неполным словарем (T
отсутствует) вычет
Я должен признать, что еще не считал, что аргументы были разрешены независимо друг от друга, и поэтому, чем в этом последнем случае, при вычислении D 1 компилятор не мог воспользоваться тем, что D 0 уже вывел значение для String
. Почему это делается таким образом, однако, вероятно, это полный вопрос.
Без внешнего шаблона он работает, так как в нем печатается "Specialized":
#include <iostream>
#include <vector>
struct First {
template <typename T>
using ArrayType = std::vector<T>;
};
template <typename T>
struct Second {
void go() { std::cout << "General\n"; }
};
template <typename T>
struct Second < typename First::template ArrayType<T> > {
void go() { std::cout << "Specialized\n"; }
};
int main() {
Second < std::vector<int> > second;
second.go();
return 0;
}
Без внутреннего шаблона он работает, так как в нем печатается "Specialized":
#include <iostream>
#include <vector>
template <typename String>
struct First {
using ArrayType = std::vector<int>;
};
template <typename String, typename T>
struct Second {
void go() { std::cout << "General\n"; }
};
template <typename String>
struct Second < String, typename First<String>::ArrayType > {
void go() { std::cout << "Specialized\n"; }
};
int main() {
Second < std::vector<int>, std::vector<int> > second;
second.go();
return 0;
}
В обоих случаях он не работает, так как в нем печатается "Общее":
#include <iostream>
#include <vector>
template <typename String>
struct First {
template <typename T>
using ArrayType = std::vector<T>;
};
template <typename String, typename T>
struct Second {
void go() { std::cout << "General\n"; }
};
template <typename String, typename T>
struct Second < String, typename First<String>::template ArrayType<T> > {
void go() { std::cout << "Specialized\n"; }
};
int main() {
Second < std::vector<char>, std::vector<int> > second;
second.go();
return 0;
}
Ответ 4
Альтернативно, какой лучший (самый маленький/самый чистый) способ сделать эту работу?
Возможно, это:
- Напишите шаблон шаблона SFINAE
Tr<String,Allocator,T>
, который определяет, является ли T
так же, как basic_data_object<String,Allocator>::array_container<T::E>
для некоторого типа E
- если таковой существует - это T::value_type
.
- Предоставить шаблон
get_data_object_value
с 4-м параметром
по умолчанию Tr<String,Allocator,T>::value
- Напишите частичную специализацию
get_data_object_value
, создающую
4-й параметр как true
, false
соответственно.
Вот демонстрационная программа:
#include <type_traits>
#include <vector>
#include <iostream>
template <typename String, template<class> class Allocator>
struct basic_data_object
{
template<typename T>
using array_container = std::vector<T, Allocator<T>>;
};
template<typename T, typename String, template<class> class Allocator>
struct is_basic_data_object_array_container
/*
A trait template that has a `static const bool` member `value` equal to
`true` if and only if parameter type `T` is a container type
with `value_type E` s.t.
`T` = `basic_data_object<String,Allocator>::array_container<T::E>`
*/
{
template<typename A>
static constexpr bool
test(std::is_same<
A,
typename basic_data_object<String,Allocator>::template
array_container<typename A::value_type>
> *) {
return std::is_same<
A,
typename basic_data_object<String,Allocator>::template
array_container<typename A::value_type>
>::value;
}
template<typename A>
static constexpr bool test(...) {
return false;
}
static const bool value = test<T>(nullptr);
};
template <
typename String,
template<class> class Allocator,
typename T,
bool Select =
is_basic_data_object_array_container<T,String,Allocator>::value
>
struct get_data_object_value;
template <
typename String,
template<class> class Allocator,
typename T
>
struct get_data_object_value<
String,
Allocator,
T,
false
>
{
static void demo() {
std::cout << "Is NOT a basic_data_object array_container" << std::endl;
}
};
template <
typename String,
template<class> class Allocator,
typename T>
struct get_data_object_value<
String,
Allocator,
T,
true
>
{
static void demo() {
std::cout << "Is a basic_data_object array_container" << std::endl;
}
};
#include <list>
#include <memory>
using namespace std;
int main(int argc, char **argv)
{
get_data_object_value<string,allocator,std::vector<short>>::demo();
get_data_object_value<string,allocator,std::list<short>>::demo();
get_data_object_value<string,allocator,short>::demo();
return 0;
}
Построено с gcc 4.8.2, clang 3.4. Выход:
Is a basic_data_object array_container
Is NOT a basic_data_object array_container
Is NOT a basic_data_object array_container
VС++ 2013 не будет компилировать это из-за отсутствия поддержки constexpr
. Чтобы учесть это
компилятор может использовать следующую менее естественную реализацию признака:
template<typename T, typename String, template<class> class Allocator>
struct is_basic_data_object_array_container
{
template<typename A>
static
auto test(
std::is_same<
A,
typename basic_data_object<String, Allocator>::template
array_container<typename A::value_type>
> *
) ->
std::integral_constant<
bool,
std::is_same<
A,
typename basic_data_object<String, Allocator>::template
array_container<typename A::value_type>
>::value
>{}
template<typename A>
static std::false_type test(...);
using type = decltype(test<T>(nullptr));
static const bool value = type::value;
};
Ответ 5
Изменить. Этот ответ работает только из-за ошибки в GCC 4.8.1
Ваш код работает как ожидалось, если вы отбросите ключевое слово template
по вашей специализации:
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
{
void foo() { std::cout << "general" << std::endl; }
};
template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
typename basic_data_object<String, Allocator>::array_container<T>>
// ^^^^^^ no template!
{
void foo() { std::cout << "special" << std::endl; }
};
Пример, протестированный с помощью GCC 4.8.1:
int main() {
get_data_object_value<std::string,std::allocator,std::vector<int>> obj;
obj.foo(); // prints "special"
}