c++language-lawyerc++20sfinaec++-concepts

C++20 SFINAE trailing vs leading requires in member function in a class template


I have a C++17 piece of SFINAE code

#include <type_traits>

template<typename>
constexpr bool dependentFalse = false;

template<typename T>
struct IAmEmpty
{
    template<typename U=T, std::enable_if_t<dependentFalse<U>>* =nullptr>
    auto deleteMe()
    {}

};

template struct IAmEmpty<int>;

https://godbolt.org/z/vWW3favWj

I am trying to rework that to C++20 and I wonder:

#include <type_traits>

template<typename T>
struct IAmEmpty
{
    template<typename U=T>
    requires false
    void deleteMe() {}

    void deleteMeToo() requires false {}
};

template struct IAmEmpty<int>;

As far as I understand it, leading requires is close to pre-C++20 SFINAE achieved using e.g. enable_if, void_t and the like. Therefore deleteMe is actually a member function template. deleteMeToo is not a function template (or am I wrong here?). Live example: https://godbolt.org/z/rGzx8dz3f

Are there any situations when deleteMe and deleteMeToo actually behave differently and should one care when preferring trailing requires over the leading one?


Solution

  • when preferring trailing requires over the leading one?

    template <..> requires (..) void foo(); is equivalent to template <..> void foo() requires (..); So it is just style (or possibly convenience to use function parameter in requires).

    Non template function only allows requires after.

    Are there any situations when deleteMe and deleteMeToo actually behave differently and should one care?

    One is template function whereas the other is not (they are still template entities, from the class).

    And template and non-template functions have some differences. Template has/allows

    For migration, there are no points to use template (which was added only for SFINAE), so

    // C++17
    template<typename T>
    struct IAmEmpty
    {
        template <typename U=T, std::enable_if_t<some_condition_v<U>>* = nullptr>
        auto deleteMe()
        {}
    };
    

    would become

    // C++20
    template<typename T>
    struct IAmEmpty
    {
        auto deleteMe() requires some_condition_v<T> {}
    };
    

    You might consider to turn traits some_condition_v into concepts to allow subsumption.