Узнать тип на этапе компиляции

139
24 июня 2019, 11:10

Можно ли определить тип переданных параметров variadic templates, на этапе компиляции?

Хочу использовать static_assert для проверки допустимости передачи параметра в шаблон.

Допустим запретить передачу в шаблон типа float

int main() {
    MyClass <int, double, int, char> obj; // так можно
    MyClass <double, char> obj;          // так тоже
    MyClass <int, float> obj;            // а вот так нельзя
    return 0;
} 
Answer 1

Как уже ответил @AnT, в C++17 появились fold expressions.

Если использование C++17 недоступно, то можно сделать следующим образом:

template<typename ... Args>
struct any_of;    
template<typename CheckType, typename HeadType, typename ... Args>
struct any_of<CheckType, HeadType, Args...>: any_of<CheckType, Args...> {};    
template<typename CheckType, typename ... Args>
struct any_of<CheckType, CheckType, Args...>: std::true_type {};    
template<typename CheckType>
struct any_of<CheckType>: std::false_type {};
//использование
template<typename ... Args>
struct MyClass
{
    static_assert(!any_of<float, Args...>::value, "float not supported");
};
//...
MyClass<int, double> d1;//ok
MyClass<float, int> d2;//assertion

Для понимания работы этого кода предлагаю разобраться сначала с возможной реализацией is_same для двух типов:

//Общая версия шаблона наследуется от std::false_type,
//что дает сразу готовый член value и операцию преобразования к bool.
//false_type::value имеет значение false
template<typename T, typename U>
struct my_is_same: std::false_type
{};
//Весь "фокус" в данной специализации.
//Данная специализация будет выбрана, если типы переданные в шаблон одинаковые (T и T).
//И эта специализация наследуется от std::true_type, 
///и член value равен true
template<typename T>
struct my_is_same<T, T>: std::true_type
{};
//...
static_assert(my_is_same<float, float>::value, "failure"); //ок - my_is_same здесь наследник std::true_type
static_assert(my_is_same<float, int>::value, "failure"); //ошибка - my_is_same здесь наследник std::false_type

Думаю, прием с наследованием от false_type и true_type понятен.

В начальном коде используется еще один прием - наследование в шаблоне класса от самого себя с другим набором аргументов шаблона. Разберем и его на примере первоначального кода.

//Общая версия шаблона не реализуется и не используется
//Вместо нее будут использоваться специализации
template<typename ... Args>
struct any_of;
//Данная специализация выбирается если параметров более одного.
//any_of в данном случае наследуется от any_of, при этом отсекается параметр HeadType,
//т.е. он не передается в качестве аргумента шаблону базового класса.
//Таким образом с каждой "ступенью" наследования отсекается один тип из списка аргументов.
//при этом отсекается второй тип (HeadType), т.к. первый (CheckType) -
//это тот тип, который необходимо проверить.
template<typename CheckType, typename HeadType, typename ... Args>
struct any_of<CheckType, HeadType, Args...>: any_of<CheckType, Args...> {};
//Наследование и "отсечение" будет происходить до тех пор, 
//пока не упремся в одну из следующих специализаций:

//Эта специализация будет выбрана, если первый и второй параметры шаблона одинаковы.
//Здесь уже используется наследование от std::true_type, 
//что прерывает цепочку наследования от any_of,
//таким образом any_of становится наследником std::true_type, 
//если хоть один аргументов, переданных изначально в any_of, совпадает с первым аргументом.
template<typename CheckType, typename ... Args>
struct any_of<CheckType, CheckType, Args...>: std::true_type {};
//если же мы дошли до такого момента, когда остался только проверяемый тип,
//значит в списке аргументов any_of не было типов, совпадающих с первым,
//поэтому прерываем наследование от any_of наследованием от std::false_type.
template<typename CheckType>
struct any_of<CheckType>: std::false_type {};
//Таким образом, если в пакете Args... в any_of<Type, Args...> есть тип Type, 
//то any_of<Type, Args...> станет наследником std::true_type,
//в ином случае any_of<Type, Args...> станет наследником false_type
static_assert(any_of<float, int, float>::value, "float not supported");//ok - any_of наследник std::true_type
static_assert(any_of<float, int, double>::value, "float not supported");//ошибка - any_of наследник std::false_type
Answer 2

Весь template type deduction работает именно на этапе компиляции. То есть, когда вы определяете объект шаблонного класса, компилятор скомпилирует используемый класс именно с подставленным типом в шаблонный параметр. Если вы по каким-то причинам хотите запретить пользователю использовать в качестве параметра какой-то определенный тип, то вы должны запретить конструктор для данного шаблона с таким параметром (типом). Пример :

 template <typename T>
 class Foo
 {
 public:
   T a;
 };
template<typename int>
class Foo
{
public:
 Foo() = delete;
};

Если вы попытаетесь создать объект Foo<int>, то компилятор выдаст ошибку. Отвечая на вторую часть вопроса про static_assert. В данном случае можно попробовать определить тип именно так

 static_assert(std::is_same<decltype(foo.a), bool>::value, "retval must be bool");

Принцип использования должен быть понятен

Answer 3

Для этого фактически и предназначены fold expressions в C++17

#include <type_traits>
template <typename... Args>
class MyClass
{
  static_assert(!(... || std::is_same_v<Args, float>));
};
READ ALSO
Qt C++ Несколько диапазонов с разрывом в QSpinBox, QDoubleSpinBox

Qt C++ Несколько диапазонов с разрывом в QSpinBox, QDoubleSpinBox

Как в QSpinBox, QDoubleSpinBox задать некоторый множественный диапазон? Примеры как должен работать этот SpinBox, аналогично для DoubleSpinBox: 1) Должен принимать...

143
Ошибка С2065: необъявленный идентификатор

Ошибка С2065: необъявленный идентификатор

Столкнулся с проблемой, но на существующих топиках об этой проблеме не нашел решенияЯ в затруднении, все include'ы правильно расставлены вроде,...

119
сортировка std::set c++

сортировка std::set c++

у меня есть std::set с кастомным компаратором, в set я кладу свой тип данных, который содержит два параметра, уникальность должна обеспечиваться...

160
Элемент Point (org.springframework.data.geo.Point) сохраняется в Postgresql как Bytea. Почему и как мне сохранить координаты точки?

Элемент Point (org.springframework.data.geo.Point) сохраняется в Postgresql как Bytea. Почему и как мне сохранить координаты точки?

Начал изучать Java и Spring frameworkДелаю задание, часть которого - хранение набора точек с привязкой к какой-то площади (не важно к какой, к вопросу...

210