C++ SFINAE с decltype: ошибка подстановки становится ошибкой?

этот код работает:

// Code A
#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;

template <typename T>
struct S {
    template <typename Iter, typename = typename enable_if<is_constructible<T, decltype(*(declval<Iter>()))>::value>::type>
    S(Iter) { cout << "S(Iter)" << endl; }

    S(int) { cout << "S(int)" << endl; }
};

int main()
{
    vector<int> v;
    S<int> s1(v.begin()); // stdout: S(Iter)
    S<int> s2(1);         // stdout: S(int)
}

но этот код ниже не работает. В приведенном ниже коде я просто хочу наследовать std::enable_if, Так что класс is_iter_of будет иметь член typedef type если выбран вариант std::enable_if имеет член typedef type.

// Code B
#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;

template <typename Iter, typename Target>
struct is_iter_of : public enable_if<is_constructible<Target, decltype(*(declval<Iter>()))>::value> {}

template <typename T>
struct S {
    template <typename Iter, typename = typename is_iter_of<Iter, T>::type>
    S(Iter) { cout << "S(Iter)" << endl; }

    S(int) { cout << "S(int)" << endl; }
};

int main()
{
    vector<int> v;
    S<int> s1(v.begin());
    S<int> s2(1);   // this is line 22, error
}

сообщение об ошибке:

In instantiation of 'struct is_iter_of<int, int>':
12:30:   required by substitution of 'template<class Iter, class>     S<T>::S(Iter) [with Iter = int; <template-parameter-1-2> = <missing>]'
22:16:   required from here
8:72: error: invalid type argument of unary '*' (have 'int')

сообщение об ошибке сбивает с толку: конечно, я хочу, чтобы замена шаблона не удалась.. таким образом, можно выбрать правильный конструктор. Почему SFINAE не работает в Code B? Если invalid type argument of unary '*' (have 'int') оскорбляет компилятор, компилятор должен был выдать ту же ошибку для Code A как хорошо.

2 ответов


дело в том, что вы пытаетесь расширить от std::enable_if, но выражение, которое вы помещаете в enable if, может быть недопустимым. Поскольку вы используете класс, который наследует форму, класс, который вы создаете, наследуется от недопустимого выражения, следовательно, ошибка.

простое решение для того, чтобы иметь имя enable_if выражение будет использовать псевдоним вместо класс:

template <typename Iter, typename Target>
using is_iter_of = enable_if<is_constructible<Target, decltype(*(declval<Iter>()))>::value>;

SFINAE по-прежнему будет работать, как ожидалось с псевдонимом.

это поскольку создание экземпляра псевдонима является частью функции, на которую вы пытаетесь применить SFINAE. С наследованием, выражение является частью класса instanciated, а не функция. Вот почему вы получили жесткую ошибку.

дело в том, что в вашем случае применяется несколько способов SFINAE. Давайте посмотрим, где SFINAE может произойти:

enable_if< //             here -------v
    is_constructible<Target, decltype(*(declval<Iter>()))>::value
>::type
//  ^--- here

действительно, SFINAE произойдет потому что enable_if::type не будет существовать, если параметр bool равен false, вызывая SFINAE.

но если вы посмотрите внимательно, другой тип может не существовать:decltype(*(std::declval<Iter>())). Если Iter is int запрос типа Звездный оператора нет смысла. Так что SFINAE, если применяется там тоже.

ваше решение с наследованием будет работать, если каждый класс, который будет отправлять как Iter имел * оператора. С int он не существует, вы отправляете несуществующий тип в std::is_constructible, что делает недопустимым все выражение, формирующее базовый класс.

С псевдонимом, все выражение использования std::enable_if подлежит применению SFINAE. В то время как подход базового класса будет применяться только SFINAE на результат std::enable_if.


проблема в том, что выражение *int (*(declval<Iter>())) недопустимо, поэтому ваш шаблон завершается ошибкой. Вам нужен другой уровень шаблонов, поэтому я предлагаю подход void_t:

  • сделать is_iter_of концепцией-lite, которая происходит от true_type или fals_type

  • использовать enable_if в определении вашего класса, чтобы включить конструктор итератора.

главное понять, что ваш конструктор раньше нужен был тип для typename is_iter_of<Iter, T>::type кроме того, что ваши enable_if на struct is_iter_of из-за чего все это было плохо сформировано. И поскольку не было откатного шаблона, у вас была ошибка компилятора.

template<class...>
using voider = void;

template <typename Iter, typename Target, typename = void>
struct is_iter_of : std::false_type{};

template <typename Iter, typename Target>
struct is_iter_of<Iter, Target, voider<decltype(*(declval<Iter>()))>> : std::is_constructible<Target, decltype(*(declval<Iter>()))> {};

template <typename T>
struct S {
    template <typename Iter, typename std::enable_if<is_iter_of<Iter, T>::value, int>::type = 0>
    S(Iter) { cout << "S(Iter)" << endl; }

    S(int) { cout << "S(int)" << endl; }
};

демо (C++11)


что происходит

дополнительные voider делает специализацию шаблона не предпочтительной, если *(declval<Iter>()) - это плохо сформированные выражения (*int) и поэтому резервный базовый шаблон (std::false_type) является выбранный.

Else, он будет получен из std::is_constructible``. In other words, it can still derive fromstd:: false_typeif the expression is well-formed but it's not constructibe, andtrue_type` в противном случае.