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
существует только в том случае, когда параметр boolean template равен true. Но почему g++ не рассматривает это как SFINAE? Я думаю, что сообщение об ошибке перегрузки возникает из-за проблемы, когда g++ не удаляет вторую функцию-член и считает, что это должно быть перегрузка.
Ответы
Ответ 1
SFINAE работает только в том случае, если подстановка в аргументе дедукции аргумента шаблона делает конструкцию плохо сформированной. Такой замены нет.
Я тоже об этом подумал и попытался использовать std::is_same< T, int >::value
и ! std::is_same< T, int >::value
, который дает тот же результат.
Это потому, что, когда экземпляр шаблона класса (который возникает, когда вы создаете объект типа Y<int>
среди других случаев), он создает экземпляры всех его объявлений-членов (не обязательно их определения/тела!). Среди них также есть шаблоны участников. Обратите внимание, что T
известно тогда, а !std::is_same< T, int >::value
дает false. Таким образом, он создаст класс 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 происходит так, как ожидалось. См. этот вопрос и соответствующий ответ о том, как это сделать.
Ответ 2
Я сделал этот короткий пример, который также работает.
#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;
}
Комментарий, если вы хотите, чтобы я уточнил. Я думаю, что код более или менее понятен, но опять же я сделал это, чтобы быть может:)
Вы можете увидеть его в действии здесь.
Ответ 3
Для тех, кто хочет найти решение, которое "просто работает":
#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
Ответ 4
Из this сообщение:
Аргументы шаблона по умолчанию не являются частью сигнатуры шаблона
Но можно сделать что-то вроде этого:
#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
}
Ответ 5
Один из способов решения этой проблемы - специализация функций-членов - ставить специализацию в другой класс, а затем наследовать от этого класса. Возможно, вам придется изменить порядок наследования, чтобы получить доступ ко всем другим базовым данным, но этот метод действительно работает.
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.
};
Ответ 6
Логическое значение должно зависеть от выводимого параметра шаблона. Таким образом, простой способ исправить заключается в использовании логического параметра по умолчанию:
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