SFINAE не работает с признаками промежуточного типа - PullRequest
0 голосов
/ 29 августа 2018

Рассмотрим следующий тестовый код:

// Preprocessor
#include <iostream>
#include <type_traits>

// Structure with no type alias
template <class T>
struct invalid {
};

// Structure with a type alias
template <class T>
struct valid {
    using type = T;
};

// Traits getting the type of the first type
template <class T, class... Args>
struct traits {
    using type = typename T::type;
};

// One argument function
template <class T, class = typename traits<T>::type>
void function(T) {
    std::cout << "function(T)" << std::endl;
}

// Two arguments function
template <class T, class U, class = typename traits<T, U>::type>
void function(T, U) {
    std::cout << "function(T, U)" << std::endl;
}

// When function can be called on all arguments
template <
    class... Args,
    class = decltype(function(std::declval<Args>()...))
>
void sfinae(Args&&... args) {
    function(std::forward<Args>(args)...);
    std::cout << "sfinae(Args&&...)" << std::endl;
}

// When function can be called on all arguments except the first one
template <
    class T,
    class... Args,
    class = decltype(function(std::declval<Args>()...))
>
void sfinae(const invalid<T>&, Args&&... args) {
    function(std::forward<Args>(args)...);
    std::cout << "sfinae(const invalid<T>&, Args&&...)" << std::endl;
}

// Main function
int main(int argc, char* argv[]) {
    valid<int> v;
    invalid<int> i;
    sfinae(v);
    sfinae(i, v);
    return 0;
}

Код включает в себя:

  • структура invalid, которая не имеет ::type
  • структура valid, которая имеет ::type
  • структура traits, которая определяет ::type как T::type
  • перегруженный function, который должен работать, только если тип первого аргумента таков, что traits<T>::type определен
  • перегруженная sfinae функция, которая должна иметь возможность вызывать function, даже если первый аргумент invalid

Однако механизм SFINAE в этом случае, похоже, не работает, и я не уверен, что понимаю почему. Ошибка следующая:

sfinae_problem_make.cpp:19:30: error: no type named 'type' in 'invalid<int>'
    using type = typename T::type;
                 ~~~~~~~~~~~~^~~~
sfinae_problem_make.cpp:29:46: note: in instantiation of template class 'traits<invalid<int>, valid<int> >' requested here
template <class T, class U, class = typename traits<T, U>::type>
                                             ^
sfinae_problem_make.cpp:30:6: note: in instantiation of default argument for 'function<invalid<int>, valid<int> >' required here
void function(T, U) {
     ^~~~~~~~~~~~~~~~
sfinae_problem_make.cpp:37:22: note: while substituting deduced template arguments into function template 'function' [with T = invalid<int>, U = valid<int>, $2 = (no value)]
    class = decltype(function(std::declval<Args>()...))
                     ^
sfinae_problem_make.cpp:39:6: note: in instantiation of default argument for 'sfinae<invalid<int> &, valid<int> &>' required here
void sfinae(Args&&... args) {
     ^~~~~~~~~~~~~~~~~~~~~~~~
sfinae_problem_make.cpp:60:5: note: while substituting deduced template arguments into function template 'sfinae' [with Args = <invalid<int> &, valid<int> &>, $1 = (no value)]
    sfinae(i, v);

Очень удивительно, что если черты удаляются из задачи:

// Preprocessor
#include <iostream>
#include <type_traits>

// Structure with no type alias
template <class T>
struct invalid {
};

// Structure with a type alias
template <class T>
struct valid {
    using type = T;
};

// Traits getting the type of the first type
template <class T, class... Args>
struct traits {
    using type = typename T::type;
};

// One argument function
template <class T, class = typename T::type>
void function(T) {
    std::cout << "function(T)" << std::endl;
}

// Two arguments function
template <class T, class U, class = typename T::type>
void function(T, U) {
    std::cout << "function(T, U)" << std::endl;
}

// When function can be called on all arguments
template <
    class... Args,
    class = decltype(function(std::declval<Args>()...))
>
void sfinae(Args&&... args) {
    function(std::forward<Args>(args)...);
    std::cout << "sfinae(Args&&...)" << std::endl;
}

// When function can be called on all arguments except the first one
template <
    class T,
    class... Args,
    class = decltype(function(std::declval<Args>()...))
>
void sfinae(const invalid<T>&, Args&&... args) {
    function(std::forward<Args>(args)...);
    std::cout << "sfinae(const invalid<T>&, Args&&...)" << std::endl;
}

// Main function
int main(int argc, char* argv[]) {
    valid<int> v;
    invalid<int> i;
    sfinae(v);
    sfinae(i, v);
    return 0;
}

тогда он работает как положено и выдает:

function(T)
sfinae(Args&&...)
function(T)
sfinae(const invalid<T>&, Args&&...)

Вопрос: Почему первая версия не работает, и есть ли способ заставить ее работать с чертами промежуточного типа?

Ответы [ 3 ]

0 голосов
/ 29 августа 2018

SFINAE требует, чтобы сбои замещения находились «в непосредственном контексте» реализации. В противном случае произойдет серьезная ошибка.

Без промежуточного типа traits создание экземпляра function<invalid<int>, valid<int>, invalid<int>::type> вызывает ошибку в непосредственном контексте, потому что invalid<int> не имеет члена с именем type, поэтому SFINAE запускается.

При использовании промежуточного типа traits ошибка возникает во время создания определения traits<invalid<int>>, поскольку для этого требуется несуществующий invalid<int>::type. Это не в непосредственном контексте, поэтому возникает серьезная ошибка.

Чтобы исправить это, вы должны убедиться, что traits всегда имеет правильное определение. Это можно сделать так:

template <class T, class = void>
struct traits {};

template <class T>
struct traits<T, std::void_t<typename T::type>> {
    using type = typename T::type;
};
0 голосов
/ 29 августа 2018

По сути, это сводится к тому, что означает «непосредственный контекст» в [temp.deduct] / 8 , правиле sfinae, которое не очень четко определено (см. cwg 1844 ):

Если подстановка приводит к неверному типу или выражению, вывод типа завершается неудачно. Недопустимый тип или выражение - это то, что было бы неправильно сформировано, с необходимостью диагностики, если оно написано с использованием замещенных аргументов. [Примечание: Если диагностика не требуется, программа по-прежнему неисправна. Проверка доступа выполняется как часть процесса замены. - примечание к концу] Только недопустимые типы и выражения в непосредственном контексте типа функции, типов параметров шаблона и ее явного спецификатора могут привести к ошибке вывода. [Примечание: Подстановка на типы и выражения может привести к таким эффектам, как создание экземпляров специализаций шаблонов классов и / или специализаций шаблонов функций, генерация неявно определенных функций и т. Д. Такие эффекты не относятся к «непосредственному контексту». »И может привести к некорректной работе программы. - конечная нота]

В этом случае, возможно, непосредственный контекст - просто увидеть, что traits<T,U>::type - это вещь, которая существует. Который это делает. Но только когда мы проходим и создаем этот тип в качестве аргумента по умолчанию, мы должны посмотреть, что такое T::type. Но это немного отстает от того, что нам действительно нужно.

Вам нужно либо принудительно завершить создание экземпляра traits, либо заставить traits не иметь псевдоним члена с именем type, если T нет. Короткая версия отписки будет просто:

template <class T, class... Args>
struct traits;

template <class T>
struct traits<valid<T>> {
    using type = T;
};

Но вы захотите что-то более надежное, чем это.

<ч />

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

template <typename T, typename... Args, typename = typename T::type>
struct traits {
    using type = typename T::type;
};

из-за [temp.param] / 15 , но с Concepts вы можете сделать:

template <typename T>
concept Typed = requires {
    typename T::type;
};

template <Typed T, typename... Args>
struct traits {
    using type = typename T::type;
};
0 голосов
/ 29 августа 2018

Если вы прочитали описание SFINAE , то вот предложение:

Только ошибки в типах и выражениях в непосредственном контексте типа функции или ее типов параметров шаблона или ее явного спецификатора (начиная с C ++ 20) являются ошибками SFINAE.

То, что traits<T, U>::type доступно в непосредственном контексте из function, а не из sfinae. Вот почему это приводит к ошибке компилятора.

...