5

I am basically trying to do the same as std::enable_if : parameter vs template parameter but I can't get my code to compile.

I had a simple first version that has the std::enable_if in the parameters and which works fine:

#include <iostream>
#include <type_traits>

template <typename T>
void foo(T t, typename std::enable_if< std::is_same<T, int>::value >::type* = 0) {
  std::cout << "int" << std::endl;
}

template <typename T>
void foo(T t, typename std::enable_if< !std::is_same<T, int>::value >::type* = 0) {
  std::cout << "not int" << std::endl;
}

int main(int argc, char* argv[])
{
  foo(10);
  foo(10.1);
  return 0;
}

But I thought it might be more concise if the template stuff was in one place and wanted the enable_if out of the function arguments.

Now if I simply move the enable_if part I get the following:

#pragma warning(1 : 4519)

#include <iostream>
#include <type_traits>

template <typename T, typename std::enable_if< std::is_same<T, int>::value >::type = 0>
void foo(T t) {
  std::cout << "int" << std::endl;
}

template <typename T, typename std::enable_if< !std::is_same<T, int>::value >::type = 0>
void foo(T t) {
  std::cout << "not int" << std::endl;
}

int main(int argc, char* argv[])
{
  foo(10);
  foo(10.1);
  return 0;
}

The #pragma warning(1 : 4519) I needed because otherwise default arguments on a function template are an error in VS2010. The problem is it still doesn't compile. The first message was: error C2783: 'void foo(T)' : could not deduce template argument for '__formal' and even though I don't want to do that I tried explicitly stating which template I want by calling it using

  foo<int, int>(10);
  foo<double, double>(10.1);

but it still doesn't work and the new error is.

error C2975: 'foo' : invalid template argument for 'unnamed-parameter', expected compile-time constant expression

I hope somebody can tell me how to fix this and of course all comments on my style and other issues my code might have are welcome. :)

Extra question: Does anybody know why VS2010 does not allow default arguments on function templates by default?

Community
  • 1
  • 1
Sarien
  • 6,647
  • 6
  • 35
  • 55

3 Answers3

11

The problem is that the second template parameter in std::enable_if defaults to void. So your doing something which is pretty much the same as:

template <typename T, void = 0>

Which makes substitution fail always. You could use a non-type template argument of type int, which you can give a default 0 value:

template <typename T, typename std::enable_if< std::is_same<T, int>::value, int >::type = 0>

Do the same for both overloads, and it will work.

Demo here.

mfontanini
  • 21,410
  • 4
  • 65
  • 73
  • 1
    Damn, my VS2010 still says error C2783: 'void foo(T)' : could not deduce template argument for '__formal' – Sarien Aug 27 '12 at 12:23
  • Well, that is the way to do it. Maybe you should update your compiler, or switch to a more standard-compliant one. – mfontanini Aug 27 '12 at 12:29
  • 1
    Alright, good to know. I'll try to wrap it in a class template to make VS happy. – Sarien Aug 27 '12 at 12:31
  • 1
    So defaulting non-type arguments is unambiguous, but defaulting type arguments is not? Interesting. – Kerrek SB Aug 27 '12 at 12:46
  • @Kerrek: Well, the "template signature", so to speak, in this answer would be `` and in your answer it's always ``. – Xeo Aug 27 '12 at 12:48
  • @Xeo: But in my case, there's only ever *one* template with signature ``, isn't there? The other one isn't viable because of SF. – Kerrek SB Aug 27 '12 at 12:53
  • @KerrekSB Xeo is right. In my solution, the second template argument can't be deducted, since it depends on the first one. Therefore you get a template. Then, while instantiating the template, everything gets resolved. The problem is that your solution was failing before instantiation. You were defining 2 functions that have the exact same signature. If would be like defining something like "template void foo();" twice. – mfontanini Aug 27 '12 at 12:54
  • @Kerrek: No, like with default function arguments, default template arguments aren't part of the "signature", they're only used when you *call* the function (or use the template). That's why a `typename enable_if::type* = 0` as a function parameter is conceptionally the same as a `typename enable_if::type = 0` as a template parameter. In both cases, the what matters for the signature is only determined at the point where the function / template is used. – Xeo Aug 27 '12 at 12:56
  • @Xeo: Oh, right, I get it -- thanks :-) So the `pointer = 0` version actually looks even cleaner, since there's no unnecessary extra type in it... – Kerrek SB Aug 27 '12 at 12:58
  • @Kerrek: `template using EnableIf = typename std::enable_if::type;` and `template>...>`. ;) – Xeo Aug 27 '12 at 14:02
  • @Xeo: yes yes yes, I already linked to that in my answer... :-) – Kerrek SB Aug 27 '12 at 15:23
5

A default = 0 makes no sense for types. Instead, you want a default type:

template <typename T, typename = typename std::enable_if<cnd>::type>
void foo(T) { /* ... */ }

Now foo participates in overload resolution only if the default type for the second argument exists, i.e. only if the condition is true.

Defaultable template arguments for function templates were newly introduced in C++11, by the way. (They had simply been forgotten previously.)

Note that this gets cumbersome if you want multiple overloads, since you mustn't have repeated, identical template signatures. You could give each overload a distinct set of dummy template arguments, but that does not scale well. This post illustrates a better solution using variadic template packs (also available here).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 2
    That won't work. You'll have 2 definitions which are exactly the same, since default template parameters don't affect a function's signature. – mfontanini Aug 27 '12 at 12:15
  • This results in: error C2995: 'void foo(T)' : function template has already been defined (What mfontanini said.) – Sarien Aug 27 '12 at 12:15
  • 1
    @mfontanini: You're right. You'd have to give each overload different template arguments. Much better to do as RMF suggests [in this post](http://loungecpp.wikidot.com/tips-and-tricks:enable-if-for-c-11). – Kerrek SB Aug 27 '12 at 12:41
1

Instead of enable_if, in this situation, you could just do plain overloading:

#include <iostream>

void foo(int) {
  std::cout << "int" << std::endl;
}

template <typename T>
void foo(T) {
  std::cout << "not int" << std::endl;
}

int main(int argc, char* argv[])
{
  foo(10);
  foo(10.1);
  return 0;
}

An exact match is preferred which makes the compiler select the template in case the argument is not an int. In case you provide an int, we have two exact matches and the non-template is preferred. If you like to further constrain the template in a C++11 way, you can write:

#include <iostream>
#include <type_traits>

#define REQUIRES(...) ,class=typename std::enable_if<(__VA_ARGS__)>::type

void foo(int) {
  std::cout << "int" << std::endl;
}

template <typename T
  REQUIRES(!std::is_same<T,int>::value)
>
void foo(T) {
  std::cout << "not int" << std::endl;
}

int main(int argc, char* argv[])
{
  foo(10);
  foo(10.1);
  return 0;
}

which kicks the template out of the overload resolution set before overload resolution is done. But as I said, in case T=int, the non-template would win anyways.

sellibitze
  • 27,611
  • 3
  • 75
  • 95