What is wrong with my application of SFINAE when trying to implement a type trait?
Asked Answered
H

3

16

I needed a type trait that decays enums to their underlying type, and works the same as decay_t for all other types. I've written the following code, and apparently this is not how SFINAE works. But it is how I thought it should work, so what exactly is the problem with this code and what's the gap in my understanding of C++?

namespace detail {
    template <typename T, std::enable_if_t<!std::is_enum_v<T>>* = nullptr>
    struct BaseType {
        using Type = std::decay_t<T>;
    };

    template <typename T, std::enable_if_t<std::is_enum_v<T>>* = nullptr>
    struct BaseType {
        using Type = std::underlying_type_t<T>;
    };
}

template <class T>
using base_type_t = typename detail::BaseType<T>::Type;

The error in MSVC is completely unintelligible:

'detail::BaseType': template parameter '__formal' is incompatible with the declaration

In GCC it's a bit better - says that declarations of the second template parameter are incompatible between the two BaseType templates. But according to my understanding of SFINAE, only one should be visible at all for any given T and the other one should be malformed thanks to enable_if.

Godbolt link

Hotchpotch answered 11/5, 2022 at 7:45 Comment(2)
You may want to change your "About" to say "need a job in US - pronto" :-) Not kidding - abstract ask for help to "defend" is nonstarter. Concrete ask for individual help is actionable and someone just might be prompted to try. Yes this is from similar experience - long story.Lunular
@ZXX, thank you for your attention and the sound advice! But I thought my ask is concrete enough, there is supposed to be a link for donating to the fund I can vouch for. Isn't the link visible? That's the kind of help I'm asking for, and it will directly contribute towards improving the situation so that I could return to my home ASAP, along with millions other Ukrainians. I don't want a job in the U. S. nor do I want to leave Ukraine, I'm displaced but at least I haven't lost my job.Hotchpotch
S
19

SFINAE applied to class templates is primarily about choosing between partial specialisations of the template. The problem in your snippet is that there are no partial specialisations in sight. You define two primary class templates, with the same template name. That's a redefinition error.

To make it work, we should restructure the relationship between the trait implementations in such as way that they specialise the same template.

namespace detail {
    template <typename T, typename = void> // Non specialised case
    struct BaseType {
        using Type = std::decay_t<T>;
    };

    template <typename T>
    struct BaseType<T, std::enable_if_t<std::is_enum_v<T>>> {
        using Type = std::underlying_type_t<T>;
    };
}

template <class T>
using base_type_t = typename detail::BaseType<T>::Type;

The specialisation provides a void type for the second argument (just like the primary would be instantiated with). But because it does so in a "special way", partial ordering considers it more specialised. When substitution fails (we don't pass an enum), the primary becomes the fallback.

You can provide as many such specialisation as you want, so long as the second template argument is always void, and all specialisations have mutually exclusive conditions.

Sacerdotal answered 11/5, 2022 at 7:59 Comment(0)
T
11

BaseType isn't being partial-specialized, you're just redeclaring it, and since the non-type parameter has a different type, the compilation fails. you might want to do

#include <type_traits>

namespace detail {
  template <typename T, bool = std::is_enum_v<T>>
    struct BaseType;

    template <typename T>
    struct BaseType<T, false> {
      using Type = std::decay_t<T>;
    };

    template <typename T>
    struct BaseType<T, true> {
      using Type = std::underlying_type_t<T>;
    };
}
Treadway answered 11/5, 2022 at 7:58 Comment(0)
G
8

You declare the same struct with different parameter, which is forbidden.

You can do it with partial specialization:

namespace detail {
    template <typename T, typename Enabler = void>
    struct BaseType {
        using Type = std::decay_t<T>;
    };

    template <typename E>
    struct BaseType<E, std::enable_if_t<std::is_enum_v<E>>>
    {
        using Type = std::underlying_type_t<E>;
    };
}

Demo

Grouse answered 11/5, 2022 at 7:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.