Подтвердить что ты не робот

Специализация шаблона для члена шаблона класса шаблона

Это, вероятно, только проблема синтаксиса.

Итак, у меня есть этот класс шаблона:

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>>
{
};

Чего я пропустил?:)


Альтернативно, какой лучший (самый маленький/самый чистый) способ сделать эту работу?

4b9b3361

Ответ 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"
}