2

I'm trying to build a function template, say util::caller, to apply elements stored in std::vector<T> to a function that accepts those elements as arguments. For example, I have a function int func(int a, int b, int c) and a vector of int std::vector<int> args = {1, 2, 3}, a function call might be like the following code snippet.

int func(int a, int b, int c) {
  return a + b + c;
}

int main() {
  std::vector<int> args = {1, 2, 3};
  util::caller(func, args);
  return 0;
}

The implementation of util::caller is almost done, whose signature looks like:

template <typename FuncType,
          typename VecType,
          size_t... I,
          typename Traits = function_traits<FuncType>,
          typename ReturnT = typename Traits::result_type>
ReturnT caller(FuncType& func,
                VecType& args,
           indices<I...> placeholder = BuildIndices<Traits::arity>());

The definition of stuffs, such as function_traits and BuildIndices, are in the later part of this post.


The compiler reports an unexpected error when I call func like util::caller(func, args), but everything is fine when I call func like util::caller(func, args, BuildIndices<3>()). Please kindly note that in the case of int func(int, int, int), Traits::arity equals to 3UL. That is to say, the two calling of util::caller are just the same!

This confused me a lot, and I'm not sure if this is a bug of compiler. (gcc, clang, icc, msvc will all report that unexpected error.) Could anyone explain this? Any clues or hints would be appreciated.


MWE could be found at https://gcc.godbolt.org/z/JwHk6_ or:

#include <iostream>
#include <utility>
#include <vector>

namespace util {
template <typename ReturnType, typename... Args>
struct function_traits_defs {
  static constexpr size_t arity = sizeof...(Args);

  using result_type = ReturnType;

  template <size_t i>
  struct arg {
    using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
  };
};

template <typename T>
struct function_traits_impl;

template <typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(Args...)>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(*)(Args...)>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...)>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const&&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) volatile&&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits_impl<ReturnType(ClassType::*)(Args...) const volatile&&>
    : function_traits_defs<ReturnType, Args...> {};

template <typename T, typename V = void>
struct function_traits
    : function_traits_impl<T> {};

template <typename T>
struct function_traits<T, decltype((void)&T::operator())>
    : function_traits_impl<decltype(&T::operator())> {};

template <size_t... Indices>
struct indices {
  using next = indices<Indices..., sizeof...(Indices)>;
};
template <size_t N>
struct build_indices {
  using type = typename build_indices<N - 1>::type::next;
};
template <>
struct build_indices<0> {
  using type = indices<>;
};
template <size_t N>
using BuildIndices = typename build_indices<N>::type;

template <typename FuncType,
          typename VecType,
          size_t... I,
          typename Traits = function_traits<FuncType>,
          typename ReturnT = typename Traits::result_type>
ReturnT caller(FuncType& func,
                VecType& args,
           indices<I...> placeholder = BuildIndices<Traits::arity>()) {
  return func(args[I]...);
}

template <typename FuncType>
static constexpr size_t arity(FuncType& func) {
  return function_traits<FuncType>::arity;
}
}  // namespace util

int func(int a, int b, int c) {
  return a + b + c;
}

int main() {
  std::vector<int> args = {1, 2, 3};

  int j = util::caller(func, args);  // reports error
  // works fine for the following calling
  // int j = util::caller(func, args, util::BuildIndices<3>());
  // int j = util::caller(func, args, util::BuildIndices<util::arity(func)>());
  // int j = util::caller(func, args, util::BuildIndices<util::function_traits<decltype(func)>::arity>());
  std::cout << j << std::endl;

  return 0;
}

Compiler error reports:

gcc 9.1:

<source>: In function 'ReturnT util::caller(FuncType&, VecType&, util::indices<I ...>) [with FuncType = int(int, int, int); VecType = std::vector<int>; long unsigned int ...I = {}; Traits = util::function_traits<int(int, int, int), void>; ReturnT = int]':

<source>:116:34: error: could not convert 'util::BuildIndices<3>()' from 'indices<#'nontype_argument_pack' not supported by dump_expr#<expression error>>' to 'indices<#'nontype_argument_pack' not supported by dump_expr#<expression error>>'

  116 |   int j = util::caller(func, args);  // reports error

      |                                  ^

      |                                  |

      |                                  indices<#'nontype_argument_pack' not supported by dump_expr#<expression error>>

<source>:116:34: note:   when instantiating default argument for call to 'ReturnT util::caller(FuncType&, VecType&, util::indices<I ...>) [with FuncType = int(int, int, int); VecType = std::vector<int>; long unsigned int ...I = {}; Traits = util::function_traits<int(int, int, int), void>; ReturnT = int]'

<source>: In function 'int main()':

<source>:116:34: error: could not convert 'util::BuildIndices<3>()' from 'indices<#'nontype_argument_pack' not supported by dump_expr#<expression error>>' to 'indices<#'nontype_argument_pack' not supported by dump_expr#<expression error>>'

Compiler returned: 1

clang 8.0.0:

<source>:99:26: error: no viable conversion from 'indices<0UL aka 0, 1UL aka 1, sizeof...(Indices) aka 2>' to 'indices<(no argument), (no argument), (no argument)>'

           indices<I...> placeholder = BuildIndices<Traits::arity>()) {

                         ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

<source>:116:11: note: in instantiation of default function argument expression for 'caller<int (int, int, int), std::vector<int, std::allocator<int> >, util::function_traits<int (int, int, int), void>, int>' required here

  int j = util::caller(func, args);  // reports error

          ^

<source>:78:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'BuildIndices<function_traits<int (int, int, int), void>::arity>' (aka 'indices<0UL, 1UL, sizeof...(Indices)>') to 'const util::indices<> &' for 1st argument

struct indices {

       ^

<source>:78:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'BuildIndices<function_traits<int (int, int, int), void>::arity>' (aka 'indices<0UL, 1UL, sizeof...(Indices)>') to 'util::indices<> &&' for 1st argument

struct indices {

       ^

<source>:99:26: note: passing argument to parameter 'placeholder' here

           indices<I...> placeholder = BuildIndices<Traits::arity>()) {

                         ^

<source>:100:25: error: too few arguments to function call, expected 3, have 0

  return func(args[I]...);

         ~~~~           ^

<source>:116:17: note: in instantiation of function template specialization 'util::caller<int (int, int, int), std::vector<int, std::allocator<int> >, util::function_traits<int (int, int, int), void>, int>' requested here

  int = util::caller(func, args);  // reports error

                ^

2 errors generated.

Compiler returned: 1

icc 19.0.1:

<source>(99): error: no suitable user-defined conversion from "util::BuildIndices<3UL>" to "util::indices<>" exists

             indices<I...> placeholder = BuildIndices<Traits::arity>()) {

                                         ^

          detected during instantiation of "ReturnT util::caller(FuncType &, VecType &, util::indices<I...>) [with FuncType=int (int, int, int), VecType=std::vector<int, std::allocator<int>>, I=<>, Traits=util::function_traits<int (int, int, int), void>, ReturnT=int]" at line 116

<source>(100): error #165: too few arguments in function call

    return func(args[I]...);

                          ^

          detected during instantiation of "ReturnT util::caller(FuncType &, VecType &, util::indices<I...>) [with FuncType=int (int, int, int), VecType=std::vector<int, std::allocator<int>>, I=<>, Traits=util::function_traits<int (int, int, int), void>, ReturnT=int]" at line 116

compilation aborted for <source> (code 2)

Compiler returned: 2

msvc 19.21:

example.cpp

<source>(99): error C2440: 'default argument': cannot convert from 'util::indices<0,1,2>' to 'util::indices<>'

<source>(99): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

Compiler returned: 2
0

2 Answers 2

4

I think this is not a bug. The compiler is not deducing I as you expected because it is not supposed to deduce template parameters based on default arguments, as mentioned in case (4) of the non-deduced contexts on cppreference.com.

That said, it is not hard to make your code work as you expected, as long as you manually overload caller (instead of using a default argument).

template <typename FuncType,
          typename VecType,
          size_t... I,
          typename Traits = function_traits<FuncType>,
          typename ReturnT = typename Traits::result_type>
ReturnT caller(FuncType& func,
                VecType& args,
           indices<I...> placeholder) {
  return func(args[I]...);
}

template <typename FuncType, typename VecType>
typename function_traits<FuncType>::result_type caller(
    FuncType& func, VecType& args) {
  return caller(func, args, BuildIndices<function_traits<FuncType>::arity>());
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Blaok, that's the point. Thanks for your carefully reading of cppreference and the answer. Yes, just after I noticed the problem posted in the question, I figured out this workaround. Hence accepted.
4

Think about this being called with two arguments:

template <typename FuncType,
          typename VecType,
          size_t... I,
          typename Traits = function_traits<FuncType>,
          typename ReturnT = typename Traits::result_type>
ReturnT caller(FuncType& func,
               VecType& args,
               indices<I...> placeholder = BuildIndices<Traits::arity>());

There are basically two ways for that. One way explicitly specifies the template parameters (at least the first three). The other way, which you use, leaves determining the template parameters to the compiler. The compiler does that from the given arguments. For the first two parameters, it's easy, they match exactly the given arguments. However, for the third template parameter, that doesn't work because that one depends on the third argument which in turn depends on the third template parameter.

Suggestion: Can't you use Traits::arity in place of I?

Notes:

  • The all uppercase I name formally matches what is typically reserved to macro names.
  • Yes, the compiler message all suck.

7 Comments

Thanks Ulrich. All I here will be replaced to Idx later. I'm not quite understand why compiler could not determine the third template parameter. It could be determined in the following path: 1. determining FuncType to be int(*)(int, int, int); 2. determining Traits to be function_traits<FuncType>; 3. deducing BuildIndices<Traits::arity> to be indices<0, 1, 2>; 4. based on the third function parameter, determining the parameter pack to be 0, 1, 2, which is the third parameter in template.
Also, I'm not quite understand your words: use Traits::arity in place of I. How to do this?
I could easily be wrong, that much up front. I do think that your reasoning has a flaw though: The default value for the third function argument is defined indirectly. However, I doubt that this implies the type of this argument. One thing I'd try is to code the third function parameter as template parameter, just like the first two.
Concerning the Traits::arity in place of I, I believe both are integers and they both have the same value. Don't they?
No... I is a parameter pack here, while Traits::arity is an unsigned long int. They are not the same thing.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.