0

The code below doesn't compile, it complains about "can't convert string to int" when I call func with type Foo and "can't convert int to string" when I call func with type Bar. I thought I already used std::is_same to tell if the type is a Foo or Bar, why this seems to be not working? What would be a better way to do this?

class Foo {
  Foo(int foo){}
};

class Bar {
  Bar(string foo){}
};

template<typename T>
void func(){
  if(std::is_same<T, Foo>::value) {
    T t(1);
  } else {
    T t("aaa");
  }
}

func<Foo>();
func<Bar>();
flyerc
  • 25
  • 2

4 Answers4

5

There is no static if in C++, so the code has to be compilable even if the branch is not taken.

You may solve that with specialization:

template<typename T>
void func(){
    T t("aaa");
}

template<>
void func<Foo>(){
    Foo t(1);
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • What is `no static if`? If he change the code to: `int isSame = std::is_same::value;` and then use `if(isSame)`, the code will work? – Đăng Khoa Huỳnh Apr 06 '16 at 08:04
  • 2
    @ĐăngKhoaHuỳnh: That means `if` is a construct which is executed at runtime, not a compile-time. So no matter what `std::is_same::value` returns, both branches should be compilable. – Nawaz Apr 06 '16 at 08:06
  • 1
    @ĐăngKhoaHuỳnh "no static if" means that the compiler won't evaluate the condition in the `if` and thus will not know which branch should be taken, so all branches of the `if` have to be evaluated by the compiler. You can have "almost static if" by using [`std::enable_if`](http://en.cppreference.com/w/cpp/types/enable_if) though. – Holt Apr 06 '16 at 08:07
  • Note that C++17 looks like it's going to get `if constexpr (cond)`, where the branch is chosen at compile time. – TartanLlama Apr 06 '16 at 08:08
  • Oh no, don't tell people to specialize function templates as a first attempt to solve a problem. Template function specialization works differently than class template specialization and function overriding, and is very very rarely a good idea. – Yakk - Adam Nevraumont Apr 06 '16 at 14:21
3

If you want a more general solution than the one proposed by Jarod42, you could use std::enable_if (since c++11):

template<typename T>
typename std::enable_if<std::is_constructible<T, int>::value, void>::type func() {
    T t(1);
}

template<typename T>
typename std::enable_if<std::is_constructible<T, const char *>::value, void>::type func() {
    T t("abc");
}

This way, the compiler will only generate function where enable_if is true (this is a "almost static if").

You can use std::enable_if to check a lot of things, if you only need to check if the instantiation T(1) is valid you could use SFINAE expressions:

template<typename T>
decltype(T(1), void()) func(){
    T t(1);
}

template<typename T>
decltype(T(std::string()), void()) func() {
    T t("abc");
}

See also std::is_constructible.

Community
  • 1
  • 1
Holt
  • 36,600
  • 7
  • 92
  • 139
1

Tag dispatching is the way to go.

template<class T>
void func_impl( std::true_type T_is_Foo ) {
  T t(1);
}
template<class T>
void func_impl( std::false_type T_is_Foo ) {
  T t("aaa");
}
template<typename T>
void func(){
  return func_impl( std::is_same<T,Foo>{} );
}

You can mess around with template function specialization or SFINAE; both are fragile. Template function specialization behaves unlike similar features elsewhere in C++, and SFINAE is impenetrable.

Another option in C++14 is to write a static if using lambdas. This is impenetrable, but at least isolated from your actual code:

struct do_nothing{ template<class...Ts> void operator()(Ts&&...){} };

template<class F_true, class F_false=do_nothing>
auto static_if( std::true_type, F_true&& f_true, F_false&& f_false = do_nothing{} ) {
  return std::forward<F_true>(f_true);
}
template<class F_true, class F_false=do_nothing>
auto static_if( std::false_type, F_true&& f_true, F_false&& f_false = do_nothing{} ) {
  return std::forward<F_false>(f_false);
}

template<typename T>
void func(){
  static_if( std::is_same<T,Foo>{}, [&](auto&&){
    T t(1);
  }, [&](auto&&){
    T t("abc");
  })(1);
}

which at least looks like an if statement. static_if returns the second or third argument depending on the static truth value of its first argument.

In this case, we pass in lambdas. We then invoke the return value.

Only the lambda that matches the truth value of the first argument is invoked.

Barry
  • 286,269
  • 29
  • 621
  • 977
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Wouldn't the lambda body have to be compiled anyway, even if you're not invoking it? – Barry Apr 06 '16 at 22:09
  • @barry right: code originally took an instance of a type we tested and passed it in as a `auto&&`. Was lost in transcription. Hack injected that will work with all current compilers, will dehack it. – Yakk - Adam Nevraumont Apr 06 '16 at 22:19
  • Oh god. That's... truly impenetrable. Don't know whether to be horrified or impressed. – Barry Apr 06 '16 at 22:35
0

You could use SFINAE and create 2 overloaded functions for different T types

typename = std::enable_if<std::is_same<...>::value>::type
typename = std::enable_if_t<std::is_same<...>::value> // for c++14


template<typename T>
typename std::enable_if<std::is_same<Foo, T>::value>::type
    func()
    {
        T t(4);
    }

template<typename T>
typename std::enable_if<!std::is_same<Foo, T>::value>::type
    func()
    {
        T t("4");
    }
jonezq
  • 344
  • 1
  • 5