Странное поведение MSVC с std:: эксперимент::::is_detected

Я реализовал std::experimental::is_detected на основе этой статьи на cppreference.com (часть кода приведена ниже + рабочее воспроизведение).

Он хорошо работает на G++ и Clang++, но приводит к странному ошибочному поведению с MSVC: is_detected кажется, всегда bool_constant<true>!

Здесь вы можете увидеть правильный результат, используя gcc 5.x: @ ideone.com

Но с MSVC 19 (поставляется с VS2015) тесты всегда проходят успешно:

Z:\>cl /EHsc test.cxx
....
Z:\>test
true, true

Итак, это известная ошибка в компиляторе? Связано ли это с тем, что выражение SFINAE не выполняется правильно? Есть ли обходной путь, который я могу использовать, чтобы сделать эту работу?

Спасибо!


Вот часть кода, который воспроизводит ошибку (я опустил остальную часть интерфейса, кроме is_detected повысить разборчивость):

#include <iostream>

// void_t: void type alias
template< typename... >
using void_t = void;
//

namespace internal
{
    // Fallback case
    template<   typename D,
                typename Void,
                template< typename... > class Check,
                typename... Args 
            >
    struct detect_impl
    {
        using value_t = std::false_type;
        using type = D;
    };


    // Check succeeded
    template<   typename D,
                template< typename... > class Check,
                typename... Args 
            >
    struct detect_impl
        < D, void_t< Check<Args...> >, Check, Args... >
    {
        using value_t = std::true_type;
        using type = Check<Args...>;
    };
}

// Type representing a missing type.
struct nonesuch
{
    nonesuch() = delete;
    ~nonesuch() = delete;
    nonesuch(nonesuch const&) = delete;
    void operator=(nonesuch const&) = delete;
};


template<   template< typename... > class Check,
            typename... Args
        >
using is_detected = typename internal::detect_impl< nonesuch, void, Check, Args... >::value_t;



// Our test
template< typename T >
using is_addable_impl = decltype( std::declval<T>() + std::declval<T>() );

template< typename T >
using is_addable = is_detected<is_addable_impl, T>;


auto main(int argc, const char* arv[])
    -> int
{
    std::cout   <<  std::boolalpha
                <<  is_addable<int>::value  << ", "
                <<  is_addable<nonesuch>::value << std::endl;
}

РЕДАКТИРОВАТЬ: Странно, непосредственное использование идиомы void_t работает на MSVC:

#include <iostream>
#include <type_traits>

struct X {};

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

 template< typename T >
 struct is_addable <T, std::void_t<decltype(std::declval<T>() + std::declval<T>())>>
     : std::true_type
 {};

int main()
{
    std::cout   <<  std::boolalpha 
                << is_addable<int>::value   << ", "
                << is_addable<X>::value
                << std::endl;
}

Выход:

Z:\>cl /EHsc test.cxx
....
Z:\>test.exe
true, false

2 ответа

Решение

Вот обходной путь, который, кажется, работает с недавним MSVC (проверенный с Visual C++ 19.00.23720.0):

#include <type_traits>

template <typename...>
using void_t = void;

namespace internal
{
    template <typename V, typename D>
    struct detect_impl
    {
        using value_t = V;
        using type = D;
    };

    template <typename D, template <typename...> class Check, typename... Args>
    auto detect_check(char)
        -> detect_impl<std::false_type, D>;

    template <typename D, template <typename...> class Check, typename... Args>
    auto detect_check(int)
        -> decltype(void_t<Check<Args...>>(),
                    detect_impl<std::true_type, Check<Args...>>{});

    template <typename D, typename Void, template <typename...> class Check, typename... Args>
    struct detect : decltype(detect_check<D, Check, Args...>(0)) {};
}

struct nonesuch
{
    nonesuch() = delete;
    ~nonesuch() = delete;
    nonesuch(nonesuch const&) = delete;
    void operator=(nonesuch const&) = delete;
};

template <template< typename... > class Check, typename... Args>
using is_detected = typename internal::detect<nonesuch, void, Check, Args...>::value_t;

(Манекен void Параметр теперь не используется, он просто для того, чтобы остальная часть реализации оставалась нетронутой.)

Вам нужен компилятор C++11 для компиляции приведенного выше кода, а MSVC 2015 не является компилятором C++11.

Конкретный недостаток соответствия C++11, с которым вы сталкиваетесь, называется Microsoft "выражение SFINAE". Следите за тем, чтобы это было исправлено.

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

Обычно нет обходного пути.

Другие вопросы по тегам