Strange MSVC behaviour with std::experimental::is_

2019-05-25 03:57发布

问题:

I implemented std::experimental::is_detected based on this article on cppreference.com (Part of the code is below + working repro).

It works well on G++ and Clang++, but results in strange errornous behaviour with MSVC: is_detected seems to always be bool_constant<true>!

Here you can see the correct result using gcc 5.x: @ideone.com

But with MSVC 19 (shipped with VS2015) the tests always succeed:

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

So, is this a known bug in the compiler? Is it related to Expression SFINAE not being implemented correctly? Is there any workaround I can use to make this work?

Thank you!


Here is part of the code that reproduces the error (I omitted the rest of the interface besides is_detected to increase legibility):

#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;
}

EDIT: Strangely, directly using the void_t idiom works on 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;
}

Output:

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

回答1:

Here's a workaround that appears to work with recent MSVC (tested with 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;

(The dummy void parameter is unused now, it's there just to keep the rest of the implementation intact.)



回答2:

You need a C++11 compiler to compile the above code, and MSVC 2015 is not a C++11 compiler.

The particular deficit in C++11 compliance you are running into is called "expression SFINAE" by microsoft. Keep an eye out for it being fixed.

Basically, decltype cannot be used for SFINAE. In laymans terms, SFINAE is the technique you are using for selecting overloads of template functions or classes.

There is usually no workaround.