Объявление члена шаблона класса, принадлежащего ко всем специализациям

#c #templates #template-specialization

Вопрос:

То, что я ищу, — это способ сказать: это одинаково для всех специализаций:

 template <typename T>
struct Foo {
  using id_type = unsigned int; // this does not depend on T!
};

Foo::id_type theId; // Doesn't matter what the specialization is, id_type is always the same.
 

Я хочу получить доступ к id_type без необходимости указывать специализацию…

Комментарии:

1. Зачем вам нужно объявлять использование внутри самой структуры?

2. «Это одинаково для всех специализаций» , хотя ничто не запрещает пользователю добавлять некоторую специализацию….

3. @RoQuOTriX: Для ясности. И я хотел знать, существует ли шаблонный эквивалент статического члена класса.

Ответ №1:

Вы не можете получить именно то, о чем просите. Foo это не класс. Foo<T> это класс, для любого T .

У вас может быть база без шаблона, которая содержит id_type

 struct FooBase {
  using id_type = unsigned int;
};

template <typename T>
struct Foo : FooBase{};

FooBase::id_type theId;
 

Вы можете указать параметр по умолчанию для T

 template <typename T = struct FooPlaceholder>
struct Foo {
  using id_type = unsigned int; // this does not depend on T!
};

Foo<>::id_type theId;
 

Однако ничто не мешает мне написать явную специализацию Foo , которой не хватает (или переопределяет) id_type .

 template <> struct Foo<MyType> { };    
template <> struct Foo<MyOtherType> { int id_type = 42; };
 

Комментарии:

1. все еще можно специализироваться Foo<S> на том, что не наследуется от FooBase

2. @Caleth: Я придерживаюсь вашей второй идеи: тип по умолчанию-фиктивный.

3. @Jarod42: Классы могут использоваться final для предотвращения производного типа. Но, похоже, нет способа предотвратить явную специализацию, которая обходит исходный шаблон. Похоже на разрыв в языке…

Ответ №2:

Вместо id_type того, чтобы быть свойством класса (объявление псевдонима), вы можете сделать его отдельной характеристикой параметра шаблона шаблона:

 #include <type_traits>

// Helper: compare with std::is_same but for
//         template template parameter type arguments.
template <template <typename> typename, template <typename> typename>
struct is_same_primary_template : std::false_type {};

template <template <typename> typename TT>
struct is_same_primary_template<TT, TT> : std::true_type {};

template <template <typename> typename TT, template <typename> typename UU>
constexpr bool is_same_primary_template_v{
    is_same_primary_template<TT, UU>::value};

template <typename T> struct Foo {};

template <template <typename> typename, typename Enable = void> struct id_type;

template <template <typename> typename TT>
struct id_type<TT, std::enable_if_t<is_same_primary_template_v<TT, Foo>>> {
  using type = int;
};

// ...

template <template <typename> typename TT>
using id_type_t = typename id_type<TT>::type;

int main() { id_type_t<Foo> theId; }
 

Однако у такого подхода есть один недостаток. Поскольку признак специализирован на определенных типах, он объединяет эти типы с реализацией признака (поскольку вы хотите быть очень осторожными с расположением ваших специализаций, т. е. с расположением основного шаблона).

При написании специализации будьте осторожны с ее местоположением; или заставить ее компилироваться будет таким испытанием, которое подожжет ее самосожжение.

Ответ №3:

Просто расширение, основанное на ответе Калета и комментарии, вытекающем из этого. Вы можете как бы защитить себя от «плохих» специализаций Foo, подобных этой:

 #include <iostream>
#include <type_traits>

//-------------------------------------------------------------------------
// from Caleth

struct FooBase
{
    using id_type = unsigned int;
};

template <typename T>
struct Foo : FooBase 
{
};

FooBase::id_type theId{};

//-------------------------------------------------------------------------
// specialization bypassing FooBase 

template<>
struct Foo<char>
{
};

//-------------------------------------------------------------------------
// compile time check if someone mad a "bad" specialization, pre C  20

template<typename T>
void f(const Foo<T>amp; foo)
{
    static_assert(std::is_base_of_v<FooBase, Foo<T>>); 
}

//-------------------------------------------------------------------------
// C  20 concept to check for FooBase

template<typename T>
concept HasFooBase = std::is_base_of_v<FooBase, T>;

// only accepts types derived from FooBase
void g(const HasFooBase autoamp; foo)
{
}

//-------------------------------------------------------------------------

int main()
{
    Foo<int> foo;
    Foo<char> bar;

    f(foo);
    g(foo);

    f(bar); // won't compile, error C2607: static assertion failed
    g(bar); // won't compile, error C7602: 'g': the associated constraints are not satisfied

    return 0;
}