CONCEPT_REQUIRES_ implementation in ranges-v3
Asked Answered
R

1

6

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?

Rah answered 25/8, 2017 at 18:19 Comment(0)
M
7

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.

Mesomorph answered 25/8, 2017 at 18:48 Comment(3)
So, in the case without injected int, I understand that if a user instantiates S, the compiler will issue an error, even if the function f isn't called and thus not instantiated?Rah
From static-assert-dependent-on-non-type-template-parameter, it is even a little more subbtle "From [temp.res]/8: If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.". So we should have possible valid instantiation (which is possible by providing 43 for the magical parameter). std::enable_if_t<(some_unique_name == some_unique_name + 1) || (__VA_ARGS__)> would be wrong.Dixie
Don't you just love template metaprogramming? And here I was adding an additional template parameter typename T_ = T and doing my test on T_. Didn't even think that this would be valid.Chelate

© 2022 - 2024 — McMap. All rights reserved.