What I need write this second SFINAE constructor?
Asked Answered
B

1

5

I wanted to activate a class with parameter packs when all types in the parameter pack is distinct. I wrote a small helper function like this that works with no problem:

template<typename T, typename... Ts>
constexpr bool has_duplicate_types() {
    if constexpr (sizeof...(Ts) == 0)
        return false;
    else
        return ((std::is_same_v<std::decay_t<T>, std::decay_t<Ts>> || ...) || has_duplicate_types<Ts...>());
}

When I tried to activate my class using SFINAE, I tried several method and the working one was this:

template<typename T, typename dummy = void, typename... Ts>
struct XYZ_imp {
    XYZ_imp() {
        std::cout << "ok\n";
    }
};

template<typename T, typename... Ts>
struct XYZ_imp<T, std::enable_if_t<has_duplicate_types<T, Ts...>()>, Ts...> {
    XYZ_imp() = delete;
};

template<typename T, typename... Ts>
using XYZ = XYZ_imp<T, void, Ts...>;

But as you see, it is too much code. But when I tried to write it like this:

template<typename T, typename... Ts>
struct XYZ {
    template<typename U = T, std::enable_if_t<has_duplicate_types<U, Ts...>(), int> = 0>
    XYZ() = delete;
};

I don't know why it is not working. It works as expected when there is a similar type like XYZ<int,int> and it says constructor is deleted, but when types differ like XYZ<int,double>, I get following error:

no matching function for call to 'XYZ<int, double>::XYZ()'

If I write another SFINAE constructor like this:

template<typename T, typename... Ts>
struct XYZ {
    template<typename U = T, std::enable_if_t<has_duplicate_types<U, Ts...>(), int> = 0>
    XYZ() = delete;
    template<typename U = T, std::enable_if_t<!has_duplicate_types<U, Ts...>(), int> = 0>
    XYZ() {
        std::cout << "ok\n";
    }
};

It will work, but I have no idea why I need to write 2nd one and it does not pick defaulted one without it automatically. Any idea why?

Bucovina answered 7/2, 2022 at 6:33 Comment(3)
Can you use C++20? If yes, put that condition into a requires. If no, static_assert is the easiest option (but it makes your class SFINAE-unfriendly).Weatherford
@Weatherford yea, I thought it will be much easier in concepts, But I wanted to know a little about classical SFINAE and why class does not work really.Bucovina
I think sfinea doesn't mean the user defined default constructor disappears. And since there's a user defined one, there is no default/trivial one anymore.Galvanism
W
7

If I write another SFINAE constructor like this: ... It will work, but I have no idea why I need to write 2nd one and it does not pick defaulted one without it automatically.

The compiler doesn't generate the default constructor if you define any custom constructor, even if that constructor is disabled by SFINAE.

And since you need to write the non-=deleted constructor, you might as well remove the =deleted one:

template<typename T, typename... Ts>
struct XYZ
{
    template <typename U = T, std::enable_if_t<!has_duplicate_types<U, Ts...>(), int> = 0>
    XYZ()
    {
        std::cout << "ok\n";
    }
};
Weatherford answered 7/2, 2022 at 6:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.