Default template argument when using std::enable_if as templ. param.: why OK with two template functions that differ only in the enable_if parameter? [duplicate]
Asked Answered
S

2

17

In the language reference of std::enable_if at cppreference the following note is included

Notes

A common mistake is to declare two function templates that differ only in their default template arguments. This is illegal because default template arguments are not part of function template's signature, and declaring two different function templates with the same signature is illegal.

In the template functions in example below, it seems to me that this situation occurs. I.e., the two template functions onlyForDerivedObjects(...) seem (to me) to differ only by their default template arguments. I realize I am missing something here, and hopefully someone can explain this to me, or point me in the direction to where I might find an epiphany for myself.

  • Question: W.r.t. the quote above, why do the example below compile and run fine: do I misclassify the typename std::enable_if ... part in the template functions below when I consider it to yield a situation with two template functions which differ only in their default template argument?

Example

Base and derived classes:

class BaseA
{
public:
  int getInt() const { return 21; };
};

class DerivedA : public BaseA {};

class BaseB
{
public:
  int getAnotherInt() const { return 33; };
};

class DerivedB : public BaseB {};

with the following template functions

/* template functions that, seemingly, only differ in their
   default template arguments? */
template< class T,
          typename std::enable_if<std::is_base_of<BaseA, T>::value>::type* = nullptr >
int onlyForDerivedObjects(const T& obj)
{
  return 2*obj.getInt();
}

template< class T,
          typename std::enable_if<std::is_base_of<BaseB, T>::value>::type* = nullptr >
int onlyForDerivedObjects(const T& obj)
{
  return 3*obj.getAnotherInt();
}

compiles and runs fine (g++ -Wall -std=c++11 ..., g++ 4.9.3)

#include <iostream>
#include <type_traits>

/* ... classes and template functions as above */

/* template argument deduction seems to work fine */
int main()
{
  DerivedA* objA = new DerivedA();
  DerivedB* objB = new DerivedB();

  std::cout << onlyForDerivedObjects(*objA) << std::endl; // 42
  std::cout << onlyForDerivedObjects(*objB) << std::endl; // 99

  return 0;
}
Swordsman answered 11/7, 2016 at 10:59 Comment(0)
L
17

Notes

A common mistake is to declare two function templates that differ only in their default template arguments. This is illegal because default template arguments are not part of function template's signature, and declaring two different function templates with the same signature is illegal.

Your functions don't differ only in their default template arguments, they differ in their template parameters, so have different signatures.

In both cases the default template argument is nullptr, but the second template parameter is different in each case.

Lannielanning answered 11/7, 2016 at 11:3 Comment(2)
can you elaborate, how they are different? as to my understanding: both types get resolved to void* or nothing. And for both cases default value is nullptr, so as for me they are exactly the same.Bartholomew
@DawidPilarski the signature includes the entire enable_if type, not just what it resolves to after deduction. Those types are different, because they depend on different expressions (one uses is_base_of<BaseA, T> and one uses is_base_of<BaseB, T>).Lannielanning
K
9

The common mistake is:

template <typename T, typename = std::enable_if_t<cond>>
void foo()

template <typename T, typename = std::enable_if_t<!cond>>
void foo()

which both declare

template <typename, typename>
void foo();
Keim answered 11/7, 2016 at 11:4 Comment(2)
struct T { enum { int_t, float_t } type; template <typename Integer, std::enable_if_t<std::is_integral<Integer>::value, bool> = true > T(Integer) : type(int_t) {} template <typename Floating, std::enable_if_t<std::is_floating_point<Floating>::value, bool> = true > T(Floating) : type(float_t) {} // OK }; @Keim based on your answer above, can you please help clarify why above CPP ref example works?Contrail
You have 2 distinct constructors: template <typename U, std::enable_if_t<std::is_integral<U>>, bool> T(U); and template <typename U, std::enable_if_t<std::is_floating_point<U>>> T(U);Keim

© 2022 - 2024 — McMap. All rights reserved.