5

This link doesn't answer my question so I'll ask it here:

Basically I want to write a template function

template <typename Out, typename In>
Out f(In x);

Here I always need to specify Out when calling f. I don't want to do it every time, so I basically want

template <typename Out = In, typename In>
Out f(In x);

Which means if I don't specify Out, it will default to In. However, this is not possible in C++11.

So my question is, is there any way to achieve the effect:

  1. calling f(t) will instantiate f<T,T>(t) or more generally f<typename SomeThing<T>::type, T>
  2. calling f<U>(t) will instantiate f<U, T>(t)
Community
  • 1
  • 1
Kan Li
  • 8,557
  • 8
  • 53
  • 93
  • What is the difference between `` and ``? – Alex Chamberlain Jan 24 '13 at 19:09
  • @AlexChamberlain the first order does not know how to set the default of `Out` to `In` because it hasn't seen `In` yet at that point. – TemplateRex Jan 24 '13 at 19:21
  • @icando why is reversing the order of template arguments not an option for you? see my answer. (other answers that proposed this are gone so I missed the arguments why they were not satisfactory) – TemplateRex Jan 24 '13 at 19:32

3 Answers3

8

You probably never want to specify In but rather have it deduced, right?

In this case you need to overload the function:

template <typename Out, In>
Out f(In x);

template <typename T>
T f(T x);

Call it:

f(42);
f<float>(42);

… but unfortunately that’s ambiguous for f<int>(42). No matter, we can use SFINAE to disable one of the overloads appropriately:

template <
    typename Out,
    typename In,
    typename = typename std::enable_if<not std::is_same<Out, In>::value>::type
>
Out f(In x);

template <typename T>
T f(T x);

In order to avoid redundancy in the implementation, let both functions dispatch to a common implementation, f_impl.

Here’s a working example:

template <typename Out, typename In>
Out f_impl(In x) {
    std::cout << "f<" << typeid(Out).name() <<
                 ", " << typeid(In).name() <<
                 ">(" << x << ")\n";
    return x;
}

template <
    typename Out,
    typename In,
    typename = typename std::enable_if<not std::is_same<Out, In>::value>::type
>
Out f(In x) {
    std::cout << "f<Out, In>(x):\t ";
    return f_impl<Out, In>(x);
}

template <typename T>
T f(T x) {
    std::cout << "f<T>(x):\t ";
    return f_impl<T, T>(x);
}


int main() {
    f(42);
    f<float>(42);
    f<int>(42);
}
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • What happens if I call `f(4.2)`? – K-ballo Jan 24 '13 at 19:15
  • 2
    @K-ballo The correct thing. [Try it.](http://stacked-crooked.com/view?id=7b5fc26fb42eb06e33f844e2c0a805dc) – Konrad Rudolph Jan 24 '13 at 19:17
  • This generally looks good with a little drawback: f(5) will cause compiler error: ambiguous overload. Anyway to solve the issue? – Kan Li Jan 24 '13 at 19:22
  • @icando Good point, it can be solved with a `disable_if`. I’ll adapt my examples. – Konrad Rudolph Jan 24 '13 at 19:24
  • Also both overloads are actually valid in case of f(42). Could you point out which part of the standard guarantees f is prefered over f? – Kan Li Jan 24 '13 at 19:25
  • 1
    @icando I can’t point to the section but one of the overloads requires an implicit conversion of `42` to `float` while the other is an exact match so there’s no ambiguity. – Konrad Rudolph Jan 24 '13 at 19:27
  • @icando: In that case both might be valid, but `f` is a better match, since it perfectly matches the type the argument, while `f` needs an implicit conversion. Should be in the overload resolution section – Grizzly Jan 24 '13 at 19:29
  • @KonradRudolph, still imperfect with your answer: `f(5)` or `f(5)` will both be ambiguous. Maybe I shouldn't be so picky, but I think it is quite challenging to make it perfect, and it will be fun if we can do it :-) – Kan Li Jan 24 '13 at 19:46
  • @KonradRudolph, I think the essential imperfection with your answer is, ideally `f(5)` should go to the case `f`, even `f(5)` should go to the case `f`, but the `f` or `f` are the perfect overload resolutions so you have to disable `f` instead. Maybe there is just no perfect solution under current C++. – Kan Li Jan 24 '13 at 19:54
  • I have a perfect solution below now. Take a look at it. – Kan Li Jan 24 '13 at 20:36
  • 2
    You can replace the second prototype of f with `template T f(T x)` so it is ignored when you specify a template parameter (then you can remove the enable_if). – Marc Glisse Jan 24 '13 at 21:20
  • @Marc That’s f***ing smart. – Konrad Rudolph Jan 24 '13 at 22:31
4

You may not need it here, but here is a classical technique:

struct Default
{
  template <typename Argument, typename Value>
    struct Get {
      typedef Argument type;
    };

  template <typename Value>
    struct Get <Default, Value> {
      typedef Value type;
    };
};

template <typename Out = Default, typename In>
typename Default::Get<Out, In>::type f(In x);
Marc Glisse
  • 7,550
  • 2
  • 30
  • 53
  • This still requires the existance of a type that you can't put as `Out`. For example, calling `f(5)` will still goes to `f` instead of `f`. This is not what I want. If there exists such type that I can't put into the parameter list, then I can do a lot of things, e.g. disable_if. But such requirement makes it imperfect. – Kan Li Jan 24 '13 at 19:31
  • If I make Default non-movable, you won't be able to declare a function that returns it anyway, so there is no loss here. – Marc Glisse Jan 24 '13 at 19:47
  • You could even use a preexisting non-movable type like std::mutex as the dummy type. `f` is already meaningless, nothing to lose. – Marc Glisse Jan 24 '13 at 20:00
2

I have a PERFECT solution here! f<const int&> won't work because a function can't return a reference to a temporary, not related to the techniques used here.

[hidden]$ cat a.cpp
#include <iostream>
#include <type_traits>
#include <typeinfo>
using namespace std;

template <typename Out, typename In>
Out f_impl(In x) {
  cout << "Out=" << typeid(Out).name() << " " << "In=" << typeid(In).name() << endl;
  return Out();
}

template <typename T, typename... Args>
struct FirstOf {
  typedef T type;
};

template <typename T, typename U>
struct SecondOf {
  typedef U type;
};

template <typename... Args, typename In>
typename enable_if<sizeof...(Args) <= 1, typename FirstOf<Args..., In>::type>::type f(In x) {
  typedef typename FirstOf<Args..., In>::type Out;
  return f_impl<Out, In>(x);
}

template <typename... Args, typename In>
typename enable_if<sizeof...(Args) == 2, typename FirstOf<Args...>::type>::type f(In x) {
  typedef typename FirstOf<Args...>::type Out;
  typedef typename SecondOf<Args...>::type RealIn;
  return f_impl<Out, RealIn>(x);
}

int main() {
  f(1);
  f(1.0);
  f<double>(1);
  f<int>(1.0);
  f<int>(1);
  f<const int>(1);
  f<int, double>(1);
  f<int, int>(1);
  f<double, double>(1);
}
[hidden]$ g++ -std=c++11 a.cpp
[hidden]$ ./a.out
Out=i In=i
Out=d In=d
Out=d In=i
Out=i In=d
Out=i In=i
Out=i In=i
Out=i In=d
Out=i In=i
Out=d In=d
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Kan Li
  • 8,557
  • 8
  • 53
  • 93
  • I'm a bit surprised `template ` is legal. :) And shouldn't you use perfect forwarding on `f(In x)` when forwarding to `f_impl`? – Yakk - Adam Nevraumont Jan 25 '13 at 01:11
  • I am surprised too. I am very surprised that by using varadic template one can actually tell whether you are calling with f(x) or f(x) or f(x), etc. – Kan Li Jan 25 '13 at 03:19