2

Lets sat that I have 2 functions:

int foo(const int, const float);
int bar(const int, const char);

Now I want to overload a veradic template function based on whether it matches one of these functions. So for example:

template <typename... T>
decltype(foo(declval<T>()...) func(T... args);

template <typename... T>
decltype(bar(declval<T>()...) func(T... args);

But I'm getting the error:

error C2995: 'unknown-type func(T...)': function template has already been defined

Because the T must be defined differently for each I would have assumed this was a valid overload, but it seems like it's not :( Can someone help me allow this overload?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 2
    What should happen for `func('a', 'b')`? Both `foo('a', 'b')` and `bar('a', 'b')` are valid calls. – Igor Tandetnik May 06 '19 at 15:08
  • `tag dispatch` ? You can't overload based on a single typename, from the compiler's point of view they are identical. Introduce another template parameter, add a tag for each of the functions by introducing a unique type and then dispatch – KostasRim May 06 '19 at 15:13
  • 1
    @KostasRim I'm not certain I understand the statement "You can't overload based on a single typename" You're saying that the actual function declarations don't boil down to `int func(int, float)` and `int func(int, char)` in the compiler? – Jonathan Mee May 06 '19 at 15:25
  • @JonathanMee no all I am saying that this `template decltype(foo(declval()) func(T... args);` is the same as this `template decltype(bar(declval()) func(T... args);` and they differ only in what they return. That is, `T is a placeholder, not a concrete type`. That's why you need to tag dispatch. Basically you redefining the same template with a different return statement – KostasRim May 06 '19 at 15:27
  • @KostasRim they should trigger SFINAE and thus only one should be defined. – Jonathan Mee May 06 '19 at 15:29
  • 1
    @JonathanMee Edit, my bad, sorry I thought the two functions return different type. You are right – KostasRim May 06 '19 at 15:29

1 Answers1

2

Suppose you call func(0,0). During overload resolution both of these are considered:

template <typename... T>
decltype(foo(declval<T>()...) func(T... args);

template <typename... T>
decltype(bar(declval<T>()...) func(T... args);

substitution is done:

template <typename... T = {int, int}>
decltype(foo(declval<int>(),declval<int>()) func(int,  int);

template <typename... T = {int, int}>
decltype(bar(declval<int>(),declval<int>()) func(int, int);

foo and bar calls are evaluated, then decltype'd:

template <typename... T = {int, int}>
int func(int,  int);

template <typename... T = {int, int}>
int func(int, int);

notice that these are identical signatures. The compiler complains, you aren't allowed to do this.

How you got to identical signatures is, in a sense, immaterial.

You can write a trait that reads "you can call bar with these arguments". Suppose you do it.

template<class...Ts>
constexpr bool can_call_bar_with = /* some expression */;

and

template<class...Ts>
constexpr bool can_call_foo_with = /* some expression */;

now we can do this:

template <typename... T,
  std::enable_if_t< can_call_foo_with<T...>, bool> = true
>
int func(T... args);

template <typename... T,
  std::enable_if_t< can_call_bar_with<T...> && ! can_call_foo_with<T...>, bool> = true
>
int func(T... args);

and now no matter what T... you pass to it, you never get two func; this is because I ensured that SFINAE makes only one signature valid.

To write

template<class...Ts>
constexpr bool can_call_bar_with = /* some expression */;

there is the is_detected or my can_apply idiom. See here.


If you want to ask "which, between foo and `bar, would be preferred in overload resolution", that is a different and more difficult problem. There is no general way; with a list of signatures, you can do it.

//you'd implement this something like:

template<class...Ts>
struct types_t {};


template<std::size_t I, class Sig>
struct make_tagged_sig;
template<std::size_t I, class Sig>
using tagged_sig = typename make_tagged_sig<I,Sig>::type;

template<std::size_t I, class...Ts>
struct make_tagged_sig<I, types_t<Ts...>> {
  using type=std::integral_constant<std::size_t,I>(Ts...);
};


template<class Sig>
struct overload_check;

template<class R, class...Args>
struct overload_check<R(Args...)> {
  R test(Args...) const;
};

template<class...Sigs>
struct overload_checker:
  overload_check<Sigs>...
{
  using overload_check<Sigs>::test...;

  template<class...Args>
  constexpr auto operator()( types_t<Args...> ) const {
    return decltype( test( std::declval<Args>()... ) ){};
  }
};

template<class Indexes, class...Sigs>
struct which_overload_helper;
template<class...Sigs>
using which_overload_helper_t = typename which_overload_helper<std::index_sequence_for<Sigs...>, Sigs...>::type;
template<std::size_t...Is, class...Sigs>
struct which_overload_helper<std::index_sequence<Is...>, Sigs...> {
    using type = overload_checker< tagged_sig<Is, Sigs>... >;
};

template<class Args, class...Sigs>
constexpr std::size_t which_overload = which_overload_helper_t<Sigs...>{}( Args{} );

Live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • But I *don't* call `func(0,0)` I call `func(0, 0.0F)` In which case only one should be defined... right? – Jonathan Mee May 06 '19 at 15:31
  • @JonathanMee do you declare and define the template later or is it a definition inside the header ? – KostasRim May 06 '19 at 15:33
  • @JonathanMee `0.F` can convert to `char` no problem. So `decltype( bar(0, 0.F) )` is `int`. – Yakk - Adam Nevraumont May 06 '19 at 15:33
  • I guess I'm confused because If I write: `int func(int, float)` and `int func(int, char)` and call `func(0, 0.0F)` that is *not* ambiguous, why then would it be in the template case? Anyway, I need to read your updated answer now... – Jonathan Mee May 06 '19 at 15:37
  • @JonathanMee You may intend your code to say "do overload resolution between `foo` and `bar`", but that isn't what *your code says*, and your code does what it says not what you mean it to say. – Yakk - Adam Nevraumont May 06 '19 at 15:39
  • @Yakk-AdamNevraumont So traits are my workaround. Not what I was hoping for, but the answer does make sense. Thank you for the explanation I'll accept shortly... – Jonathan Mee May 06 '19 at 15:42
  • @JonathanMee "Which overload" template written. It is probably what you want. – Yakk - Adam Nevraumont May 06 '19 at 17:13