Ivan Sanz Carasa
Ivan Sanz Carasa

Reputation: 1387

Expanding variadic types into another

I'm trying to expand a pack of variadic types to another type by doing something like this.

I have a simple trait that gets enabled when TFunction returns TReturn.

template<typename TReturn, typename TFunction>
using ReturnsType = enable_if_t<is_same<decltype(declval<TFunction>()()), 
                                TReturn>::value>;

it works perfectly in all main compilers (VC++, GCC, CLANG).

Now i want to create another one with this notation:

template<typename TReturn, typename... TFunctions>
using AllReturnsType = ???

so it gets enabled only if all the passed functions (TFunctions...) do return TReturn.

So I tried this implementation:

template<typename TReturn, typename... TFunctions>
using AllReturnsType = void_t<ReturnsType<TReturn, TFunctions>...>;

but it fails in VC++ (it's fine in GCC and CLANG)

code playground can be found here: https://godbolt.org/g/d839wp

Is this a bug in MSVC++? how can I make it work in all compilers?

Thanks!

Edit: I'm in contact with MSVC team members and they are taking care of it! It is a confirmed bug.

Upvotes: 3

Views: 157

Answers (2)

AndyG
AndyG

Reputation: 41100

I agree with max66 that it appears to be a MSVC bug. If I had to guess what was happening, I would say it probably comes down to the fact that MSVC still doesn't have full support for expression SFINAE.

It seems they've been making steady improvements, though, and their implementation of std::result_of is advertised to use it.

Coincidentally, what you're trying to achieve with get_return_type:

template<typename Function>
using get_return_type = decltype(declval<Function>()());

Has already been done with std::result_of (deprecated), and std::invoke_result

Since the latest MSVC appears to lack support for std::invoke_result, it appears that if we modify your code to use std::result_of instead, it all compiles just fine:

template<class Function>
using get_return_type = typename std::result_of<Function()>::type;

Demo

Upvotes: 2

max66
max66

Reputation: 66210

Is this a bug in MSVC++?

Yes, I think so.

how can I make it work in all compilers?

Don't ask me why but seems that, if you pass through an helper struct for enable_if_returns as follows

template <typename T, typename F, 
          bool = std::is_same_v<T, get_return_type<F>>>
struct enIfR
{ };

template <typename T, typename F>
struct enIfR<T, F, true>
{ using type = T; };

template <typename T, typename F>
using enable_if_returns = typename enIfR<T, F>::type;

your code compile also in MSVC.

The following is a full compiling (MSVC also) example

#include <iostream>
#include <type_traits>

template <typename Function>
using get_return_type = decltype(std::declval<Function>()());

template <typename T, typename F,
         bool = std::is_same_v<T, get_return_type<F>>>
struct enIfR
 { };

template <typename T, typename F>
struct enIfR<T, F, true>
 { using type = T; };

template <typename T, typename F>
using enable_if_returns = typename enIfR<T, F>::type;

template<typename T, typename Function>
using enable_if_returns0 = typename std::enable_if<
   std::is_same<T, get_return_type<Function>>::value>::type;

template<typename T, typename... Functions>
using enable_if_all_returns = std::void_t<typename enIfR<T, Functions>::type...>;

int main()
{
    constexpr auto fint = []() -> int { return 1; };
    constexpr auto fstring = []() -> std::string { return "asd"; };

    (enable_if_returns<int, decltype(fint)>)0; // ok
    //(enable_if_returns<int, decltype(fstring)>)0; // fail on all (expected)
    //(enIfR<int, decltype(fstring)>::type)0; // fail on all (expected)

    (enable_if_all_returns<int, decltype(fint)>)0; // fail in MSVC (NOT expected, 'get_return_type<unknown-type>' being compiled)
    //(enable_if_all_returns<int, decltype(fint), decltype(string)>)0; // fail in all (expected)

    return 0;
}

Upvotes: 2

Related Questions