Используйте параметр enable_if, который не зависит от параметра шаблона метода
Я пытаюсь использовать std::enable_if
и SFINAE для исключения реализации метода шаблона класса, основанного исключительно на параметрах шаблона класса. Пример:
#include <type_traits>
template<class T1, class T2>
class Foo {
template<class InnerT, class ... Args>
typename std::enable_if<std::is_same<T1, T2>::value, void>::type
bar(InnerT param) {};
template<class InnerT, class ... Args>
typename std::enable_if<!std::is_same<T1, T2>::value, void>::type
bar(InnerT param) {};
};
int main() {
Foo<int, int> f;
}
Здесь bar()
должен вести себя по-разному в зависимости от того, являются ли T1
и T2
одинаковыми или нет. Однако этот код не компилируется. Ни GCC, ни clang не говорят мне ничего полезного. Я подозреваю, что проблема заключается в том, что условие std::enable_if
не зависит от параметров bar()
, то есть не от его непосредственного контекста, как указано в пункте 17.8.2, пункт 8 стандарта.
Это предположение подтверждается тем фактом, что этот код компилируется отлично:
#include <type_traits>
class DummyClass {};
template<class T1, class T2>
class Foo {
template<class InnerT, class ... Args>
typename std::enable_if<std::is_same<T1, T2>::value ||
std::is_same<InnerT, DummyClass>::value, void>::type
bar(InnerT param) {};
template<class InnerT, class ... Args>
typename std::enable_if<!std::is_same<T1, T2>::value ||
std::is_same<InnerT, DummyClass>::value, void>::type
bar(InnerT param) {};
};
int main() {
Foo<int, int> f;
}
Теперь выражение внутри std::enable_if
зависит от "непосредственного контекста", а именно от InnerT
, хотя эта часть выражения всегда оценивается как false
.
Похоже, я могу использовать это в качестве обходного пути, но это кажется очень хакированным и уродливым. Как вы решаете эту проблему "правильно"? Я думал, что нужно добавить дополнительный параметр шаблона (назовем его DummyType
) в bar()
, который по умолчанию имеет значение, например, DummyType = T1
, а затем проверить std::is_same<DummyType, T2>
, но тот факт, что bar()
принимает пакет параметров делает это невозможным (или делает это??)
Ответы
Ответ 1
Вместо того, чтобы пытаться использовать SFINAE в двух реализациях, просто используйте нормальное разрешение перегрузки.
#include <type_traits>
#include <iostream>
template<class T1, class T2>
class Foo {
template<class InnerT, class ... Args>
void do_bar(InnerT param, std::true_type, Args... args) { std::cout << "same" << std::endl; }
template<class InnerT, class ... Args>
void do_bar(InnerT param, std::false_type, Args... args) { std::cout << "not same" << std::endl; }
public:
template<class InnerT, class ... Args>
void bar(InnerT&& param, Args&&... args)
{
do_bar(std::forward<InnerT>(param), std::is_same<T1, T2>{}, std::forward<Args>(args)...);
}
};
int main() {
Foo<int, int> f1;
Foo<int, double> f2;
f1.bar(1, 2, 3);
f2.bar("Hello");
}
Смотрите онлайн
Ответ 2
Чтобы перейти от комментариев:
Я думал, что нужно добавить дополнительный параметр шаблона (назовем его DummyType
) в bar()
, который по умолчанию имеет значение, например, DummyType = T1
, а затем проверить std::is_same<DummyType, T2>
, но тот факт, что bar()
принимает пакет параметров делает это невозможным (или делает это??)
Это не так. Выполнение того, что вы догадались, не сработает, будет работать.
#include <type_traits>
template<class T1, class T2>
struct Foo {
template<class InnerT, class ... Args, class DummyType = T1>
typename std::enable_if<std::is_same<DummyType, T2>::value, void>::type
bar(InnerT param) {};
template<class InnerT, class ... Args, class DummyType = T1>
typename std::enable_if<!std::is_same<DummyType, T2>::value, void>::type
bar(InnerT param) {};
};
int main() {
Foo<int, int> f;
f.bar(3); // InnerT = int; Args = empty; DummyType = int.
f.bar<int, void, short>(4); // InnerT = int; Args = void, short; DummyType = int.
}
Но если я добавлю DummyType в качестве второго параметра шаблона, а затем передам список аргументов шаблона, которые должны войти в пакет - как теперь компилятор, чтобы второй аргумент не попадал в DummyType, но первым делом, частью которого является Args?
Вот почему я добавил в качестве последнего параметра. Параметры шаблона, не входящие в пакет, могут следовать за параметрами пакета шаблонов, если они имеют значение по умолчанию. Компилятор будет использовать все явно заданные аргументы для Args
и будет использовать DummyType = T1
независимо от того, какие аргументы вы укажете.
Ответ 3
Я подозреваю, что проблема заключается в том, что условие enable_if не зависит от параметров bar,
Именно так.
Я думал, что нужно добавить дополнительный шаблонный параметр (назовем его DummyType) в bar, который по умолчанию имеет значение, например DummyType = T1, а затем проверить std :: is_same
Я обычно вижу именно это решение.
но тот факт, что бар принимает пакет параметров, делает это невозможным (или это...?)
Нет, если вы помещаете DummyType
перед InnerT
template <typename D1 = T1, typename InnerT, typename ... Args>
typename std::enable_if<std::is_same<D1, T2>::value>::type
bar (InnerT param) { std::cout << "- true version" << std::endl; }
template <typename D1 = T1, typename InnerT, typename ... Args>
typename std::enable_if<!std::is_same<D1, D2>::value>::type
bar (InnerT param) { std::cout << "- false version" << std::endl; }
Это прекрасно работает.
Недостатком этого решения является то, что вы можете "захватить" bar()
объясняя тип D1
Foo<int, int> f;
f.bar(0); // print "- true version"
f.bar<long>(0); // print "- false version"
но вы можете решить эту проблему, указав, что T1
такой же, как D1
template <typename T1, typename T2>
struct Foo {
template <typename D1 = T1, typename InnerT, typename ... Args>
typename std::enable_if< std::is_same<D1, T2>::value
&& std::is_same<D1, T1>::value>::type
bar (InnerT param) { std::cout << "- true version" << std::endl; }
template <typename D1 = T1, typename InnerT, typename ... Args>
typename std::enable_if< ! std::is_same<D1, T2>::value
&& std::is_same<D1, T1>::value>::type
bar (InnerT param) { std::cout << "- false version" << std::endl; }
};
Теперь вы больше не можете "убирать" bar()
.