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?

ABu
  • 10,423
  • 6
  • 52
  • 103

1 Answers1

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.

cpplearner
  • 13,776
  • 2
  • 47
  • 72
Casey
  • 41,449
  • 7
  • 95
  • 125
  • 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? – ABu Aug 25 '17 at 20:12
  • From [static-assert-dependent-on-non-type-template-parameter](https://stackoverflow.com/a/30079285/2684539), 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. – Jarod42 Aug 25 '17 at 21:08
  • 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. – Adrian May 08 '19 at 15:36