std::enable_if для условной компиляции функции-члена
Я пытаюсь получить простой пример для работы, чтобы понять, как использовать std::enable_if
, Прочитав этот ответ, я подумал, что не должно быть слишком сложно привести простой пример. Я хочу использовать std::enable_if
выбирать между двумя функциями-членами и разрешать использовать только одну из них.
К сожалению, следующее не компилируется с gcc 4.7 и после нескольких часов попыток я спрашиваю вас, ребята, в чем моя ошибка.
#include <utility>
#include <iostream>
template< class T >
class Y {
public:
template < typename = typename std::enable_if< true >::type >
T foo() {
return 10;
}
template < typename = typename std::enable_if< false >::type >
T foo() {
return 10;
}
};
int main() {
Y< double > y;
std::cout << y.foo() << std::endl;
}
gcc сообщает о следующих проблемах:
% LANG=C make CXXFLAGS="-std=c++0x" enable_if
g++ -std=c++0x enable_if.cpp -o enable_if
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()'
Почему g++ не удаляет неправильные экземпляры для второй функции-члена? Согласно стандарту, std::enable_if< bool, T = void >::type
существует, только когда параметр логического шаблона имеет значение true. Но почему g++ не считает это SFINAE? Я думаю, что сообщение об ошибке перегрузки происходит из-за того, что g++ не удаляет вторую функцию-член и считает, что это должно быть перегрузкой.
9 ответов
SFINAE работает только в том случае, если подстановка при выводе аргумента шаблона аргумента делает конструкцию некорректной. Там нет такой замены.
Я тоже об этом думал и пытался использовать
std::is_same< T, int >::value
а также! std::is_same< T, int >::value
что дает тот же результат.
Это потому, что когда создается экземпляр шаблона класса (что происходит, когда вы создаете объект типа Y<int>
среди других случаев), он создает все декларации своих членов (не обязательно их определения / тела!). Среди них также его шаблоны участников. Обратите внимание, что T
известен тогда, и !std::is_same< T, int >::value
дает ложь. Так что это создаст класс Y<int>
который содержит
class Y<int> {
public:
/* instantiated from
template < typename = typename std::enable_if<
std::is_same< T, int >::value >::type >
T foo() {
return 10;
}
*/
template < typename = typename std::enable_if< true >::type >
int foo();
/* instantiated from
template < typename = typename std::enable_if<
! std::is_same< T, int >::value >::type >
T foo() {
return 10;
}
*/
template < typename = typename std::enable_if< false >::type >
int foo();
};
std::enable_if<false>::type
получает доступ к несуществующему типу, так что объявление неправильно сформировано. И поэтому ваша программа недействительна.
Вам нужно сделать шаблоны членов enable_if
зависит от параметра самого шаблона элемента. Тогда объявления являются действительными, потому что весь тип все еще зависит. Когда вы пытаетесь вызвать один из них, вывод аргументов для их аргументов шаблона происходит, а SFINAE происходит, как ожидалось. Смотрите этот вопрос и соответствующий ответ о том, как это сделать.
Я сделал этот короткий пример, который также работает.
#include <iostream>
#include <type_traits>
class foo;
class bar;
template<class T>
struct check
{
template<class Q = T>
typename std::enable_if<std::is_same<Q, bar>::value, bool>::type test()
{
return true;
}
template<class Q = T>
typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type test()
{
return false;
}
};
int main()
{
check<foo> check_foo;
check<bar> check_bar;
if (!check_foo.test() && check_bar.test())
std::cout << "It works!" << std::endl;
return 0;
}
Прокомментируйте, если вы хотите, чтобы я уточнил. Я думаю, что код более или менее говорит само за себя, но опять же я сделал это, чтобы я мог ошибаться:)
Вы можете увидеть это в действии здесь.
Для тех опоздавших, которые ищут решение, которое "просто работает":
#include <utility>
#include <iostream>
template< typename T >
class Y {
template< bool cond, typename U >
using resolvedType = typename std::enable_if< cond, U >::type;
public:
template< typename U = T >
resolvedType< true, U > foo() {
return 11;
}
template< typename U = T >
resolvedType< false, U > foo() {
return 12;
}
};
int main() {
Y< double > y;
std::cout << y.foo() << std::endl;
}
Компилировать с:
g++ -std=gnu++14 test.cpp
Бег дает:
./a.out
11
Из этого поста:
Аргументы шаблона по умолчанию не являются частью подписи шаблона
Но можно сделать что-то вроде этого:
#include <iostream>
struct Foo {
template < class T,
class std::enable_if < !std::is_integral<T>::value, int >::type = 0 >
void f(const T& value)
{
std::cout << "Not int" << std::endl;
}
template<class T,
class std::enable_if<std::is_integral<T>::value, int>::type = 0>
void f(const T& value)
{
std::cout << "Int" << std::endl;
}
};
int main()
{
Foo foo;
foo.f(1);
foo.f(1.1);
// Output:
// Int
// Not int
}
Одним из способов решения этой проблемы, специализацией функций-членов, является помещение специализации в другой класс, а затем наследование от этого класса. Возможно, вам придется изменить порядок наследования, чтобы получить доступ ко всем другим базовым данным, но этот метод работает.
template< class T, bool condition> struct FooImpl;
template<class T> struct FooImpl<T, true> {
T foo() { return 10; }
};
template<class T> struct FoolImpl<T,false> {
T foo() { return 5; }
};
template< class T >
class Y : public FooImpl<T, boost::is_integer<T> > // whatever your test is goes here.
{
public:
typedef FooImpl<T, boost::is_integer<T> > inherited;
// you will need to use "inherited::" if you want to name any of the
// members of those inherited classes.
};
Недостаток этого метода заключается в том, что если вам нужно протестировать много разных вещей для разных функций-членов, вам придется создать класс для каждой и связать его в дереве наследования. Это верно для доступа к общим элементам данных.
Пример:
template<class T, bool condition> class Goo;
// repeat pattern above.
template<class T, bool condition>
class Foo<T, true> : public Goo<T, boost::test<T> > {
public:
typedef Goo<T, boost::test<T> > inherited:
// etc. etc.
};
Проблема в том, что оба объявления ничем не отличаются, кроме аргументов шаблона по умолчанию, и это не делает их отдельными объявлениями. Определение второго — это всего лишь переопределение первого.foo()
.
Вот краткое описание вариантов, от самых современных до наименее современных:
Условно скомпилировать функцию-член с помощьюrequires
пункт С++20
T foo() requires some_condition<T> {
return 10;
}
// note: Trailing requires clause could also be omitted here
// because the first function is more constrained, and overload resolution
// will select the better one.
T foo() requires (!some_condition<T>) {
return 5;
}
Обходной путь: выберите реализацию с помощью if constexpr C++17.
T foo() {
if constexpr (some_condition<T>) {
// if this is more complex, you can put it in a private member function
return 10;
}
else {
return 5;
}
}
Правильное использованиеstd::enable_if
С++11
// note: Unfortunately, we pollute the function signature with additional template
// parameters.
// note: std::enable_if_t can be used in C++14
// note: The additional U parameter is necessary to make the condition dependent
// on a template parameter; otherwise the compiler will simply emit an error
// for some_condition<T>.
template <typename U = T, typename std::enable_if<some_condition<U>, int>::type = 0>
T foo() {
return 10;
}
template <typename U = T, typename std::enable_if<!some_condition<U>, int>::type = 0>
T foo() {
return 5;
}
Миксины С++98
template <typename T, bool B>
struct mixin;
template <typename T>
struct mixin<T, true> {
T foo() { return 10; }
}
template <typename T>
struct mixin<T, false> {
T foo() { return 5; }
}
template <typename T>
struct Y : mixin<T, some_condition<T>> {};
Логическое значение должно зависеть от выводимого параметра шаблона. Поэтому простой способ исправить это использовать булевский параметр по умолчанию:
template< class T >
class Y {
public:
template < bool EnableBool = true, typename = typename std::enable_if<( std::is_same<T, double>::value && EnableBool )>::type >
T foo() {
return 10;
}
};
Однако это не сработает, если вы хотите перегрузить функцию-член. Вместо этого лучше всего использовать TICK_MEMBER_REQUIRES
из библиотеки Tick:
template< class T >
class Y {
public:
TICK_MEMBER_REQUIRES(std::is_same<T, double>::value)
T foo() {
return 10;
}
TICK_MEMBER_REQUIRES(!std::is_same<T, double>::value)
T foo() {
return 10;
}
};
Вы также можете реализовать свой собственный член требует макрос, как это (на тот случай, если вы не хотите использовать другую библиотеку):
template<long N>
struct requires_enum
{
enum class type
{
none,
all
};
};
#define MEMBER_REQUIRES(...) \
typename requires_enum<__LINE__>::type PrivateRequiresEnum ## __LINE__ = requires_enum<__LINE__>::type::none, \
class=typename std::enable_if<((PrivateRequiresEnum ## __LINE__ == requires_enum<__LINE__>::type::none) && (__VA_ARGS__))>::type
// Try this one:
#include <iostream>
#include <type_traits>
struct FooTag;
struct BarTag;
#define EnableIfFoo(T) \
template <typename Dummy = void, typename = \
typename std::enable_if<std::is_same<FooTag, T>::value, Dummy>::type>
#define EnableIfBar(T) \
template <typename Dummy = void, typename = \
typename std::enable_if<std::is_same<BarTag, T>::value, Dummy>::type>
template <typename T>
class Widget {
public:
EnableIfFoo(T)
void print() const { std::cout << "I am a Foo!" << std::endl; }
EnableIfBar(T)
void display() const { std::cout << "I am a Bar!" << std::endl; }
};
int main() {
Widget<FooTag> fw;
fw.print();
//fw.display(); // compile error !!
Widget<BarTag> bw;
bw.display();
//bw.print(); // compile error !!
return 0;
}
Вот мой минималистичный пример с использованием макроса. Используйте двойные скобкиenable_if((...))
при использовании более сложных выражений.
template<bool b, std::enable_if_t<b, int> = 0>
using helper_enable_if = int;
#define enable_if(value) typename = helper_enable_if<value>
struct Test
{
template<enable_if(false)>
void run();
}