0

I need template function which will call other function with arguments taken from std::tuple. I had written some code, which compiles properly:

#include <tuple>

template<typename ...ARGS1, typename ...ARGS2>
void foo(const std::tuple<ARGS1...> &, const std::tuple<ARGS2...> &)
{
    //call function
}

int main()
{
    std::tuple<int, bool> tuple1(0, false);
    std::tuple<double, void*, float> tuple2(0.0, nullptr, 0.0f);
    foo(tuple1, tuple2);
    return 0;
}

Now I need add one more argument which is pointer to function:

#include <tuple>

template<typename ...ARGS1, typename ...ARGS2>
void foo(const std::tuple<ARGS1...> &, const std::tuple<ARGS2...> &, void (*)(ARGS1..., ARGS2...))
{
    //call function
}

void bar(int, bool, double, void*, float)
{
}

int main()
{
    std::tuple<int, bool> tuple1(0, false);
    std::tuple<double, void*, float> tuple2(0.0, nullptr, 0.0f);
    foo(tuple1, tuple2, &bar);
    return 0;
}

I tried to do it in many different ways but compiler always returns template argument deduction/substitution failed and inconsistent parameter pack deduction with '' and ''. I don't understand, what's wrong with my code. There is not any helpful information in compiler output.

Could somebody help me write this properly?

Piotr Siupa
  • 3,929
  • 2
  • 29
  • 65
  • Why not just make the function completely generic? This problem will go away *and* you can use functors and lambdas in addition to function pointers. `template void foo(..., F func) { }` – cdhowie Dec 30 '14 at 20:37
  • 2
    The compiler tries to deduce `ARGS1` from both the first and the third argument of the function template, and gets different results. You could tell it not to try to deduce the parameter pack from the third argument, e.g. via `template struct identity_t { using type = T; }; template using non_deduced_context = typename identity_t::type;`, then a parameter of type `non_deduced_context` But that's just a workaround. – dyp Dec 30 '14 at 20:37
  • This compiles fine for me (Clang svn trunk). – Columbo Dec 30 '14 at 20:46
  • Regardless of whether `ARGS2` is deduced or not (which it should be, as opposed to `ARGS1`), this should compile. – Columbo Dec 30 '14 at 20:49
  • @dyp I still don't understand, why deductions give different results. – Piotr Siupa Dec 30 '14 at 20:51
  • 1
    @Columbo Hmmm. [temp.deduct.type]/5.7 says "A function parameter pack that does not occur at the end of the *parameter-declaration-list*." is a non-deduced context for deducing template arguments from a type. Hence, `ARGS1` should be in a non-deduced context for the third argument, but `ARGS2` should not. If `ARGS2` is deduced for the third argument, the deduction result should differ from the deduction for the second parameter, hence we get inconsistent results and a deduction failure. – dyp Dec 30 '14 at 20:55
  • @dyp I cannot see why `ARGS2` should be deduced differently. It's `{double, void*, float}` in both cases, isn't it? – Columbo Dec 30 '14 at 20:59
  • 2
    @Columbo Since template argument deduction works independently on each of the function template's function parameters, there's no way for the compiler to know where it should start/stop deducing types for `ARGS2` in the third argument. That is, the compiler cannot know that you passed a tuple of size 2 to the first argument. `ARGS1` is substituted only after the deduction of all template arguments. – dyp Dec 30 '14 at 21:02
  • @dyp Ahh, that makes sense and explains GCC's behavior. – Columbo Dec 30 '14 at 21:02
  • @Columbo But it seems (an older version of) clang++ thinks even `ARGS2` is in a non-deduced context: http://coliru.stacked-crooked.com/a/fc4f042966da45e5 (the error message doesn't make sense to me, though) – dyp Dec 30 '14 at 21:06
  • @dyp Well, it has to give some error message on why deduction failed. Perhaps someone decided at some point that a non-deduced parameter pack is designated by an empty one. – Columbo Dec 30 '14 at 21:32
  • @Columbo It's perfectly fine that a non-deduced parameter pack is empty (the Standard says it somewhere). However, I can't understand what the *substitution failure* is referring to here. `void(*)()` is a perfectly fine function pointer type. – dyp Dec 30 '14 at 21:33
  • @dyp I see. You may mean [temp.arg.explicit]/3 "A trailing template parameter pack (14.5.3) not otherwise deduced will be deduced to an empty sequence of template arguments." – Columbo Dec 30 '14 at 21:38

2 Answers2

4

Since clang++ shows a different (and strange) behaviour in this case, I'm not entirely sure of this answer. However, I think I can explain what's going on when using g++.

Let's take a look at the following example:

#include <iostream>

template<typename ...ARGS1, typename ...ARGS2>
void foo(void (*)(ARGS1..., ARGS2...))
{
    std::cout << __PRETTY_FUNCTION__ << "\n";
}

void bar(int, bool, double, void*, float) {}

int main() {
    foo(&bar);
}

__PRETTY_FUNCTION__ is a non-standard macro that I often use as a hack to allow printing the types a compiler has deduced. In this case, when compiled with g++, the program prints:

void foo(void (*)(ARGS1 ..., ARGS2 ...))
[with ARGS1 = {}; ARGS2 = {int, bool, double, void*, float}]

As we can see, g++ has not deduced anything for ARGS1, but deduced all the types of the function parameters of bar for ARGS2.


Template argument deduction works for each function parameter separately. If a template argument T is deduced from two function arguments, the deduced types must be identical (with few exceptions). Similarly, for parameter packs. Consider:

template<typename T>
void foo(T, T) {}

foo(1, 2.3)

This won't compile, since for the first function parameter, T is deduced to be int, but for the second function parameter, T is deduced to be double.

Similarly,

template<typename... T>
void foo(tuple<T...>, tuple<T...>) {}

foo(tuple<int, double>{}, tuple<bool, char>{})

There are inconsistent result for the deduction of T... for the first and second function parameter. This inconsistency results in a compilation error.


As we have seen in the first part of this answer, for a parameter like:

template<typename ...ARGS1, typename ...ARGS2>
void foo(void (*)(ARGS1..., ARGS2...))

ARGS1 won't be deduced at all, and ARGS2 will "eat" all the function parameter types of the argument you pass in. If we now deduce ARGS2 also from another function argument:

template<typename ...ARGS1, typename ...ARGS2>
void foo(void (*)(ARGS1..., ARGS2...), tuple<ARGS2...>)

Then, the deduction results for ARGS2 have to be consistent:

void bar(int, double);
tuple<int, double> t;
foo(&bar, t); // works

tuple<double> u;
foo(&bar, u) // fails: inconsistent deduction results.

A very crude way to make the code in the OP compile is to tell the compiler not to deduce ARGS2 from the third argument. This can be achieved by putting the type of the third parameter in a non-deduced context (a context where deduction does not take place):

template<typename T>
struct identity_t
{ using type = T; };

template<typename T>
using non_deduced_context = typename identity_t<T>::type;

template<typename ...ARGS1, typename ...ARGS2>
void foo(const std::tuple<ARGS1...> &, const std::tuple<ARGS2...> &,
         non_deduced_context<void (*)(ARGS1..., ARGS2...)>)
{
    //call function
}

For a parameter like some_class_template<T>::nested_type, T is in a non-deduced context. This is used in the above example, though with some syntactic sugar.


The usual way of passing a function to another function in C++ is different, though:

template<typename F>
void foo(F f)
{
    //call function, e.g.
    f(42);
}

This allows passing arbitrary function objects, including pointers to functions.

dyp
  • 38,334
  • 13
  • 112
  • 177
1

There was a good answer to my question and it vanished. I don't know why.

This is not my idea, but I don't remember the author.

template<typename ...ARGS1, typename ...ARGS2, typename ...ARGS3>
void foo(const std::tuple<ARGS1...> &, const std::tuple<ARGS2...> &, void (*)(ARGS3...))
{
    //call function
}
Piotr Siupa
  • 3,929
  • 2
  • 29
  • 65