Неожиданная ошибка компиляции в C ++: передача значения по умолчанию в параметр функции - PullRequest
2 голосов
/ 04 июля 2019

Я пытаюсь построить шаблон функции, скажем util::caller, чтобы применить элементы, хранящиеся в std::vector<T>, к функции, которая принимает эти элементы в качестве аргументов.Например, у меня есть функция int func(int a, int b, int c) и вектор int std::vector<int> args = {1, 2, 3}, вызов функции может быть похож на следующий фрагмент кода.

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, содержится в более поздней части этого сообщения.


Компилятор сообщаетнеожиданная ошибка, когда я звоню func как util::caller(func, args), но все нормально, когда я звоню func как util::caller(func, args, BuildIndices<3>()).Пожалуйста, обратите внимание, что в случае 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

Ответы [ 2 ]

4 голосов
/ 04 июля 2019

Я думаю, что это не ошибка. Компилятор не выводит I, как вы ожидали, потому что он не должен выводить параметры шаблона на основе аргументов по умолчанию, как упомянуто в случае (4) невыбранных контекстов на cppreference.com .

Тем не менее, нетрудно заставить ваш код работать так, как вы ожидали, если вы вручную перегрузите 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>());
}
4 голосов
/ 04 июля 2019

Подумайте о том, что это вызывается с двумя аргументами:

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 в верхнем регистре формально соответствует тому, что обычно зарезервировано для имен макросов.
  • Да, сообщение компилятора allсосать.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...