Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CONCEPT_REQUIRES_ implementation in ranges-v3

Trying to learn how to use Eric Niebler's ranges-v3 library, and reading the source code, I saw that macro definition:

#define CONCEPT_PP_CAT_(X, Y) X ## Y
#define CONCEPT_PP_CAT(X, Y)  CONCEPT_PP_CAT_(X, Y)

/// \addtogroup group-concepts                                                                                                                                                                  
/// @{                                                                                                                                                                                          
#define CONCEPT_REQUIRES_(...)                                                      \
    int CONCEPT_PP_CAT(_concept_requires_, __LINE__) = 42,                          \
    typename std::enable_if<                                                        \
        (CONCEPT_PP_CAT(_concept_requires_, __LINE__) == 43) || (__VA_ARGS__),      \
        int                                                                         \
    >::type = 0                                                                     \
    /**/

So, in short, a template definition like:

template<typename I, typename O,
    CONCEPT_REQUIRES_(InputIterator<I>() &&
                      WeaklyIncrementable<O>())>
void fun_signature() {}

is translated as:

template<typename I, typename O,
         int a_unique_name = 42,
         typename std::enable_if
           <false || (InputIterator<I>() &&
                      WeaklyIncrementable<O>()), int>::type = 0
        >
void fun_signature() {}

I would like to know why is that macro implement that way. Why is that integer needed, and why does it need a false || cond and not just a cond template argument?

like image 794
Peregring-lk Avatar asked Aug 25 '17 18:08

Peregring-lk


1 Answers

a template definition like ... is translated as ...

Close. It actually translates as:

template<typename I, typename O,
         int a_unique_name = 42,
         typename std::enable_if
           <a_unique_name == 43 || (InputIterator<I>() &&
                      WeaklyIncrementable<O>()), int>::type = 0
        >
void fun_signature() {}

The uniquely named int is there in order to ensure that the condition for enable_if is dependent on a template parameter, to avoid the condition being checked at template definition time instead of at instantiation time so that SFINAE can happen. Consider this class definition:

template<class T>
struct S {
    template<class U, CONCEPT_REQUIRES_(ranges::Integral<T>())>
    void f(U);
};

without the injected-unique-int, this definition would lower to:

template<class T>
struct S {
    template<class U, std::enable_if_t<ranges::Integral<T>()>>
    void f(U);
};

and since ranges::Integral<T>() isn't dependent on a parameter of this function template, compilers will diagnose that std::enable_if_t<ranges::Integral<T>()> - which lowers to typename std::enable_if<ranges::Integral<T>()>::type - is ill-formed because std::enable_if<false> contains no member named type. With the injected-unique-int, the class definition lowers to:

template<class T>
struct S {
    template<class U, int some_unique_name = 42,
        std::enable_if_t<some_unique_name == 43 || ranges::Integral<T>()>>
    void f(U);
};

now a compiler cannot perform any analysis of the enable_if_t at template definition time, since some_unique_name is a template parameter that might be specified as 43 by a user.

like image 52
Casey Avatar answered Oct 29 '22 07:10

Casey