1

I have the following code:

#include <iostream>

/*
template <class A, std::enable_if_t<!std::is_same_v<A, double>, bool> = true>
void test() {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

template <class A, std::enable_if_t<std::is_same_v<A, double>, bool> = true>
void test() {
    std::cout << "SFINAE" << std::endl;
}
*/

template <class A, typename = std::enable_if_t<!std::is_same_v<A, double>>>
void test() {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

template <class A, typename = std::enable_if_t<std::is_same_v<A, double>>>
void test() {
    std::cout << "SFINAE" << std::endl;
}

int main() {
    test<int>();
    test<double>();
}

Compiler complains

test_function_templ.cpp:21:6: error: redefinition of ‘template<class A, class> void test()’
   21 | void test() {
      |      ^~~~
test_function_templ.cpp:16:6: note: ‘template<class A, class> void test()’ previously declared here
   16 | void test() {
      |      ^~~~
test_function_templ.cpp: In function ‘int main()’:
test_function_templ.cpp:27:15: error: no matching function for call to ‘test<double>()’
   27 |  test<double>();
      |               ^
test_function_templ.cpp:16:6: note: candidate: ‘template<class A, class> void test()’
   16 | void test() {
      |      ^~~~
test_function_templ.cpp:16:6: note:   template argument deduction/substitution failed:
In file included from /opt/rh/devtoolset-10/root/usr/include/c++/10/bits/move.h:57,
                 from /opt/rh/devtoolset-10/root/usr/include/c++/10/bits/nested_exception.h:40,
                 from /opt/rh/devtoolset-10/root/usr/include/c++/10/exception:148,
                 from /opt/rh/devtoolset-10/root/usr/include/c++/10/ios:39,
                 from /opt/rh/devtoolset-10/root/usr/include/c++/10/ostream:38,
                 from /opt/rh/devtoolset-10/root/usr/include/c++/10/iostream:39,
                 from test_function_templ.cpp:1:
/opt/rh/devtoolset-10/root/usr/include/c++/10/type_traits: In substitution of ‘template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = void]’:
test_function_templ.cpp:15:20:   required from here
/opt/rh/devtoolset-10/root/usr/include/c++/10/type_traits:2554:11: error: no type named ‘type’ in ‘struct std::enable_if<false, void>’
 2554 |     using enable_if_t = typename enable_if<_Cond, _Tp>::type;
      |           ^~~~~~~~~~~

If I use the first set of function templates (commented in the code) test(), it compiles and runs as expected.

Questions

  1. I thought for the 2nd set of function templates, calling test<int>() would instantiate test<int, void>() and calling test<double>() would instantiate test<double, void>(). But compiler seems like seeing two duplicate template functions?

  2. Why the first set of function templates doesn't have any issue while second set does?

Enlico
  • 23,259
  • 6
  • 48
  • 102
HCSF
  • 2,387
  • 1
  • 14
  • 40

1 Answers1

2

The problem of the first snippet is described here (see how /* WRONG */ vs /* RIGHT */ snippets of code map to your commented and uncommented code respectively).

A common mistake is to declare two function templates that differ only in their default template arguments. This does not work because the declarations are treated as redeclarations of the same function template (default template arguments are not accounted for in function template equivalence).


Here's my understanding of why that's the case.

When the compiler sees this (the correct version)

template <class A, std::enable_if_t<!std::is_same_v<A, double>, bool> = true>
void test() {}

template <class A, std::enable_if_t<std::is_same_v<A, double>, bool> = true>
void test() {}

it doesn't see a redeclaration, because it doesn't know if the two std::enable_if_t will resolve to the same type. If it knew, then it'd be a hard compile time error, just like this is an error:

template <class A, bool = true>
void test() {}

template <class A, bool = false> // no matter the value; signature is the same
void test() {}

This doesn't exclude that the ambiguity can happen at the substitution level. For instance, there's no problem with these overload declarations, in principle

template <class A, std::enable_if_t<std::is_convertible_v<A, double>, bool> = true>
void test() {}

template <class A, std::enable_if_t<std::is_convertible_v<A, int>, bool> = true>
void test() {}

but as soon as you call test<int>(), the ambiguity will pop up, because the compiler will be able to "successfully" instantiate each of the overloads, which both result in template<int, bool = whatever>, which makes them ambiguous.

As regards the wrong version:

template <class A, typename = std::enable_if_t<!std::is_same_v<A, double>>>
void test() {}

template <class A, typename = std::enable_if_t<std::is_same_v<A, double>>>
void test() {}

the problem is that before even seeing how it's used, the compiler complains already, because default template arguments are not accounted for in function template equivalence. Indeed, a much simpler example shows the problem of the previous snippet:

template <class A, typename = typename A::foo>
void test() {}

template <class A, typename = typename A::bar>
void test() {}

Notice that, exactly like in the previous snippet, in this last snippet each of the overloads is correct by itself, until you try using it with a specific A for which the expression of the default argument doesn't make sense, thus causing a hard error.

But if you put the two overloads together, that creates an ambiguity, because default template arguments are not accounted for in function template equivalence.

HCSF
  • 2,387
  • 1
  • 14
  • 40
Enlico
  • 23,259
  • 6
  • 48
  • 102
  • Thanks for the links. What I don't get is that in the "wrong" version, when `test()` is called, I thought only `test()` was instantiated from the first function template, and the 2nd function template would not be instantiated as the substitution fails in `std::enable_if_t>`. Or function template equivalence is checked on function template declarations even before any function template is instantiated? Thanks. – HCSF Nov 15 '21 at 06:18
  • @HCSF, yes, it is. Try commenting out the two lines in main, and the error will be the same. – Enlico Nov 15 '21 at 06:20
  • I see. So in the wrong version, the compiler sees `template test()` twice in the phase of testing function template equivalence. What does the compiler see in the right version in the phase of testing function template equivalence? – HCSF Nov 15 '21 at 06:26
  • @HCSF, in the right version the compiler will see both overloads, but at the time of substitution (e.g. when you call `test()` in `main`) one of the two will fail, leaving only the other one. – Enlico Nov 15 '21 at 06:35
  • then it sounds like in the right version, in the phase of testing function template equivalence, I thought compiler would see, `template test()` and `template test()`. Then, they should be considered equivalent, no? – HCSF Nov 15 '21 at 06:40
  • @HCSF, no, that's not possible because whether `std::enable_if::type` exists or not depend on the `bool`ean you pass to `std::enable_if` as first template argument, and that depends on the usage of the template. – Enlico Nov 15 '21 at 06:43
  • I think my confusion comes from: in the wrong version, `std::enable_if::type` exists or not depend on the `bool` I pass to `std::enable_if` as the first template argument as well, and that depends on the usage of the template. no? – HCSF Nov 15 '21 at 06:46
  • @HCSF, does the edit convince you? – Enlico Nov 15 '21 at 07:20
  • I think I got it now. It seems like the only good use case for having the default template argument like `typename=std::enable_if_t<....>` is (to disable the function template for a specific condition) when the rest of the tests for function template equivalence passes so that compiler won't complain redeclarating same function template? or there is another good use case? – HCSF Nov 15 '21 at 08:14
  • 1
    @HCSF, not sure about all possible use cases, but I've seen it used to just disable functions. I think you'd really like reading [this book](http://tmplbook.com/). – Enlico Nov 15 '21 at 08:21