【问题标题】:Unexpected compile error in C++: passing default value to function parameterC++ 中的意外编译错误:将默认值传递给函数参数
【发布时间】:2019-07-04 04:36:57
【问题描述】:

我正在尝试构建一个函数模板,比如util::caller,以将存储在std::vector<T> 中的元素应用于接受这些元素作为参数的函数。比如我有一个函数int func(int a, int b, int c)和一个int向量std::vector<int> args = {1, 2, 3},函数调用可能像下面的代码sn-p。

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;
}

util::caller 的实现基本完成,其签名如下:

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>());

function_traits和BuildIndices等东西的定义在本文后面部分。


当我像util::caller(func, args) 一样调用func 时编译器会报告一个意外错误,但是当我像util::caller(func, args, BuildIndices&lt;3&gt;()) 一样调用func 时一切都很好。请注意,在int func(int, int, int) 的情况下,Traits::arity 等于3UL。也就是说util::caller这两个调用是一样的!

这让我很困惑,我不确定这是否是编译器的错误。 (gcc、clang、icc、msvc 都会报告这个意外错误。)有人能解释一下吗?任何线索或提示将不胜感激。


MWE 可以在 https://gcc.godbolt.org/z/JwHk6_ 找到,或者:

#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;
}

编译器错误报告:

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

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

    我认为这不是错误。正如cppreference.com 上的非推断上下文的情况(4)所述,编译器没有像您预期的那样推断I,因为它不应该根据默认参数推断模板参数。

    也就是说,只要您手动重载caller(而不是使用默认参数),让您的代码按预期工作并不难。

    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>());
    }
    

    【讨论】:

    • 谢谢 Blaok,这就是重点。感谢您仔细阅读 cppreference 和答案。是的,就在我注意到问题中发布的问题之后,我想出了这个解决方法。因此被接受。
    【解决方案2】:

    考虑一下用两个参数调用它:

    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>());
    

    基本上有两种方法。一种方法是显式指定模板参数(至少是前三个)。您使用的另一种方法是将模板参数的确定留给编译器。编译器根据给定的参数执行此操作。对于前两个参数,很简单,它们完全匹配给定的参数。但是,对于第三个模板参数,这不起作用,因为它依赖于第三个参数,而第三个参数又依赖于第三个模板参数。

    建议:你不能用Traits::arity代替I吗?

    注意事项:

    • 全部大写的I 名称正式匹配通常保留给宏名称的名称。
    • 是的,编译器消息很糟糕。

    【讨论】:

    • 谢谢乌尔里希。此处所有I 稍后将替换为Idx。我不太明白为什么编译器无法确定第三个模板参数。可以通过以下路径确定: 1.确定FuncType为int(*)(int, int, int); 2.确定Traits为function_traits&lt;FuncType&gt;; 3.推导BuildIndices&lt;Traits::arity&gt;为indices&lt;0, 1, 2&gt;; 4.根据第三个函数参数,确定参数包为0, 1, 2,也就是template中的第三个参数。
    • 另外,我不太明白你的话:使用Traits::arity 代替I。如何做到这一点?
    • 我很容易就错了,前面说了这么多。我确实认为您的推理有一个缺陷:第三个函数参数的默认值是间接定义的。然而,我怀疑这是否暗示了这个论点的类型。我会尝试的一件事是将第三个函数参数编码为模板参数,就像前两个一样。
    • 关于Traits::arity 代替I,我相信它们都是整数并且它们都具有相同的值。不是吗?
    • 否...I 是这里的参数包,而Traits::arity 是unsigned long int。它们不是一回事。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-28
    • 1970-01-01
    相关资源
    最近更新 更多