简体   繁体   中英

void_t and trailing return type with decltype: are they completely interchangeable?

Consider the following, basic example based on void_t :

template<typename, typename = void_t<>>
struct S: std::false_type {};

template<typename T>
struct S<T, void_t<decltype(std::declval<T>().foo())>>: std::true_type {};

It can be used as it follows:

template<typename T>
std::enable_if_t<S<T>::value> func() { }

The same can be done using trailing return type and decltype :

template<typename T>
auto func() -> decltype(std::declval<T>().foo(), void()) { }

This is true for all the examples I thought of. I failed in finding a case in which either void_t or the trailing return type with decltype can be used while its counterpart cannot.
The most complex cases can ever be resolved with a combination of trailing return type and overloading (as an example, when the detector is used to switch between two functions instead of as a trigger to disable or enable something).

Is this the case? Are they ( void_t and decltype as trailing return type plus overloading if needed) completely interchangeable?
Otherwise, what's a case in which one cannot be used to work around the constraints and I'm forced to use a specific method?

This is the metaprogramming equivalent of: should I write a function or should I just write my code inline. The reasons to prefer to write a type trait are the same as the reasons to prefer to write a function: it's more self-documenting, it's reusable, it's easier to debug. The reasons to prefer to write trailing decltype are the similar to the reasons to prefer to write code inline: it's a one-off that isn't reusable, so why put in the effort factoring it out and coming up with a sensible name for it?

But here are a bunch of reasons why you might want a type trait:

Repetition

Suppose I have a trait I want to check lots of times. Like fooable . If I write the type trait once, I can treat that as a concept:

template <class, class = void>
struct fooable : std::false_type {};

template <class T>
struct fooable<T, void_t<decltype(std::declval<T>().foo())>>
: std::true_type {};

And now I can use that same concept in tons of places:

template <class T, std::enable_if_t<fooable<T>{}>* = nullptr>
void bar(T ) { ... }    

template <class T, std::enable_if_t<fooable<T>{}>* = nullptr>
void quux(T ) { ... }

For concepts that check more than a single expression, you don't want to have to repeat it every time.

Composability

Going along with repetition, composing two different type traits is easy:

template <class T>
using fooable_and_barable = std::conjunction<fooable<T>, barable<T>>;

Composing two trailing return types requires writing out all of both expressions...

Negation

With a type trait, it's easy to check that a type doesn't satisfy a trait. That's just !fooable<T>::value . You can't write a trailing- decltype expression for checking that something is invalid. This might come up when you have two disjoint overloads:

template <class T, std::enable_if_t<fooable<T>::value>* = nullptr>
void bar(T ) { ... }

template <class T, std::enable_if_t<!fooable<T>::value>* = nullptr>
void bar(T ) { ... }

which leads nicely into...

Tag dispatch

Assuming we have a short type trait, it's a lot clearer to tag dispatch with a type trait:

template <class T> void bar(T , std::true_type fooable) { ... }
template <class T> void bar(T , std::false_type not_fooable) { ... }
template <class T> void bar(T v) { bar(v, fooable<T>{}); }

than it would be otherwise:

template <class T> auto bar(T v, int ) -> decltype(v.foo(), void()) { ... }
template <class T> void bar(T v, ... ) { ... }
template <class T> void bar(T v) { bar(v, 0); }

The 0 and int/... is a little weird, right?

static_assert

What if I don't want to SFINAE on a concept, but rather just want to hard fail with a clear message?

template <class T>
struct requires_fooability {
    static_assert(fooable<T>{}, "T must be fooable!");
};

Concepts

When (if?) we ever get concepts, obviously actually using concepts is much more powerful when it comes to everything related to metaprogramming:

template <fooable T> void bar(T ) { ... }

I used both void_t and trailing decltype when I was implementing my own homebrew version of Concepts Lite (I succeded, by the way), which required creation of many additional type traits, most of which use Detection idiom in one way or another. I used void_t, trailing decltype and preceding decltype.

As far as I understood, these options are logically equivalent, so an ideal, 100%-conforming compiler should produce the same result using all of them. The problem, however, is that a particular compiler may (and will) follow different patterns of instantiation in different cases, and some of these patterns can go way beyond internal compiler limits. For example, when I tried to make MSVC 2015 Update 2 3 detect presence of multiplication by the same type, the only solution that worked was preceding decltype:

    template<typename T>
    struct has_multiplication
    {
        static no_value test_mul(...);

        template<typename U>
        static decltype(*(U*)(0) *= std::declval<U>() * std::declval<U>()) test_mul(const U&);

        static constexpr bool value = !std::is_same<no_value, decltype(test_mul(std::declval<T>())) >::value;
    };

Every other version produced internal compiler errors, though some of them worked fine with Clang and GCC. I also had to use *(U*)(0) instead of declval , because using three declval 's in a row, though perfectly legal, was just to much for the compiler in this particular case.

My bad, I forgot. Actually I used *(U*)(0) because declval produces rvalue-ref of type, which can't be assigned to, and that's why I used this. But everything else is still valid, this version worked where others didn't.

So for now my answer would be: "they're identical, as long as your compiler thinks they are". And this is something you have to find out by testing. I hope that this will stop being an issue in the following releases of MSVC and others.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM