2

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
nshct
  • 1,197
  • 9
  • 29

2 Answers2

4

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.

Morwenn
  • 21,684
  • 12
  • 93
  • 152
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    I would say it is not a standard compliant compiler. It does support most C++11/14 features, – NathanOliver Feb 26 '16 at 20:43
  • (Look at my edit) Then why is it the case that directly applying the void_t idiom is working, but the indirect way using detect_impl is not? – nshct Feb 26 '16 at 20:44
  • @Naschkatze Maybe. The behavior of their compiler under `decltype` tends to be flaky and fragile (rather than "clearly not working"). Sometimes I get it to work, but barring access to their source, I am not able to predict it. – Yakk - Adam Nevraumont Feb 26 '16 at 21:24
  • @NathanOliver You are more charitable than I am. That is permitted. – Yakk - Adam Nevraumont Feb 26 '16 at 21:29
  • 7
    To be fair, it does not pretend to be a C++11 compiler: `__cplusplus == 199711L` ;) – melak47 Feb 29 '16 at 07:14
4

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.)

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160