Search code examples
c++templatestuplesmetaprogrammingtemplate-meta-programming

"transform apply" - using tuple elements as parameters to one function to construct a parameter pack for another


Given the following:

constexpr auto t = std::make_tuple(3, 2, 1);
template<auto x>
auto InnerF() {return -x;}

auto OuterF(auto... args) {return (args + ...);}

I can use the elements of t as parameters to InnerF directly:

InnerF<std::get<0>(t)>();

But I want to be able to write

std::apply([](auto... arg) { return OuterF(InnerF<arg>()...); }, t);

But this does not work, I get

<source>:29:44: error: no matching function for call to 'InnerF'
std::apply([](auto... arg) { return OuterF(InnerF<arg>()...) ;}, t);
.
.
.
<source>:29:44: error: no matching function for call to 'InnerF'
std::apply([](auto... arg) { return OuterF(InnerF<arg>()...) ;}, t);
                                           ^~~~~~~~~~~
<source>:23:6: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'x'
auto InnerF() {return -x;}

I don't understand why the arg is considered invalid, I tried modifying the syntax a number of ways and reimplementing apply myself, but couldn't crack it - is there any way to make this work?


Solution

  • The error is because InnerF is expecting a non-type template parameter. Those must be compile-time constants, which auto... arg are not. Using gcc 12.1, I get this more helpful error message:

    <source>:10:59: error: no matching function for call to 'InnerF<arg#0>()'
       10 |     std::apply([](auto... arg) { return OuterF(InnerF<arg>()...); }, t);
          |                                                ~~~~~~~~~~~^~
    <source>:4:6: note: candidate: 'template<auto x> auto InnerF()'
        4 | auto InnerF() {return -x;}
          |      ^~~~~~
    <source>:4:6: note:   template argument deduction/substitution failed:
    <source>:10:59: error: 'arg#0' is not a constant expression
       10 |     std::apply([](auto... arg) { return OuterF(InnerF<arg>()...); }, t);
          |                                                ~~~~~~~~~~~^~
    <source>:10:59: note: in template argument for type 'int'
    

    The solution is to write InnerF like so:

    auto InnerF(auto x) { return -x; }
    

    And call std::apply like this:

    std::apply([](auto... arg) { return OuterF(InnerF(arg)...); }, t);