Попытка специализировать шаблонную функцию на основе присутствия typedef в классе - PullRequest
0 голосов
/ 24 октября 2018

Я хочу иметь возможность настроить обработку структуры на основе присутствия типа в структуре (без написания какого-либо дополнительного кода для пользовательской структуры), например:

struct Normal_t
{
};

struct Custom_t
{
    using my_custom_type = bool;
};

Похоже, ядолжен быть в состоянии сделать что-то подобное, но это не сработает:

template <class T, typename Enabler = void>
struct has_custom_type
{
    bool operator()() { return false; }
};

template <class T>
struct has_custom_type<T, typename T::my_custom_type>
{
    bool operator()() { return true; }
};

bool b_normal = has_custom_type<Normal_t>()();  // returns false
bool b_custom = has_custom_type<Custom_t>()();  // returns false, INCORRECT? should return true?

Чего я не понимаю, так это того, что стандартная библиотека использует нечто подобное, но, по-видимому, более замысловатое для своих черт типа.Например, это работает:

template<bool test, class T = void>
struct my_enable_if
{
};

template<class T>
struct my_enable_if<true, T>
{
    using type = T;
};

template <class T, class Enabler = void>
struct foo
{
    bool operator()() { return false; }
};

template <class T>
struct foo<T, typename my_enable_if<std::is_integral<T>::value>::type>
{
    bool operator()() { return true; }
};

bool foo_float = foo<float>()();    // returns false
bool foo_int = foo<int>()();        // returns true

В обоих случаях специализация происходит на основе присутствия типа в структуре, в одном случае typename T::my_custom_type, а в другом typename my_enable_if<std::is_integral<T>::value>::type.Почему работает вторая версия, а не первая?

Я нашел этот обходной путь, используя синтаксис ... пакета параметров, но мне бы очень хотелось понять, есть ли способ сделать это, используя обычныйспециализация шаблона без использования синтаксиса пакета параметров, и если нет, то почему.

template<typename ...Args>                              
bool has_custom_type_2(Args&& ...args)      { return false; }   

template<class T, std::size_t = sizeof(T::my_custom_type)>  
bool has_custom_type_2(T&)                  { return true; }

template<class T, std::size_t = sizeof(T::my_custom_type)>  
bool has_custom_type_2(T&&)                 { return true; }    /* Need this T&& version to handle has_custom_type_2(SomeClass()) where the parameter is an rvalue */

bool b2_normal = has_custom_type_2(Normal_t()); // returns false
bool b2_custom = has_custom_type_2(Custom_t()); // returns true - CORRECT!

Ответы [ 3 ]

0 голосов
/ 24 октября 2018
template <class T, typename Enabler = void> // <== void set as default template parameter type
struct has_custom_type
{
    bool operator()() { return false; }
};

template <class T>
struct has_custom_type<T, typename T::my_custom_type>
{
    bool operator()() { return true; }
};

Специализация совпадает, когда получает параметры шаблона <T, bool>.Однако, когда вы просто указываете <T> без второго типа, он переходит к типу по умолчанию, который вы указали =void, чтобы выполнить вызов <T, void>, который не соответствует вашей bool специализации.

Живой пример, показывающий, что он совпадает с явным <T, bool>: https://godbolt.org/z/MEJvwT

0 голосов
/ 24 октября 2018

Как объяснили другие, если вы установите значение по умолчанию void для второго параметра шаблона, ваше решение будет работать, только если my_custom_type равно void.

Если my_custom_type равно bool, вы можете установить bool значение по умолчанию.Но это не лучшее решение, потому что теряет универсальность.

Чтобы быть более общим, вы можете использовать SFINAE через что-то, что не работает, если my_custom_type не существует, но возвращает всегда один и тот же тип (void, обычно) если присутствует my_custom_type.

До C ++ 17 вы можете использовать decltype(), std::declval и оператор запятой

template <class T, typename Enabler = void>
struct has_custom_type
 { bool operator()() { return false; } };

template <class T>
struct has_custom_type<T,
   decltype( std::declval<typename T::my_custom_type>(), void() )>
 { bool operator()() { return true; } };

Начиная с C ++ 17это проще, потому что вы можете использовать std::void_t (см. ответ Evg, также для использования std::true_type и std::false_type вместо определения operator()).

0 голосов
/ 24 октября 2018

Проблема в том, что вы указываете тип void по умолчанию для Enabler, но T::my_custom_type не void.Либо используйте bool в качестве типа по умолчанию, либо используйте std::void_t, который всегда возвращает void:

template <class T, typename = void>
struct has_custom_type : std::false_type { };

template <class T>
struct has_custom_type<T, std::void_t<typename T::my_custom_type>> : std::true_type { };

Этот ответ объясняет, почему типы должны совпадать.

...