Я хотел бы иметь шаблон класса, способный хранить функциональный объект, принимающий ровно «N» двойных аргументов. Этот псевдокод использует несуществующий шаблон функции std::repeated_type, который решит проблему и иллюстрирует предполагаемое использование:
template<int N>
class FunctionHolder {
public:
using function_type = std::function<int(std::repeated_type<double, N> args)>;
FunctionHolder(const function_type& arg): m_func(arg) {}
private:
const function_type& m_func;
};
int my_func(double arg1, double arg2);
void main() {
FunctionHolder<2> my_holder(my_func);
}
Я хочу, чтобы код был максимально простым и читабельным, поэтому, хотя Я смутно понимаю, что могу сшить решение, используя шаблоны std::integer_sequence и вспомогательного класса, но я не уверен, что мое решение будет достаточно простым.
Как насчет использования std::array<double, N> в качестве параметра?
Двойные аргументы «N» — это std::array<double, N> или std::initializer_list<double>.
Отправить массив или указатель на структуру.
Похоже, вам нужно godbolt.org/z/j9vchqxbx
@康桓瑋 Это действительно похоже на то, что хотел OP (я бы просто сделал это немного более общим). Не могли бы вы расширить свой комментарий в ответ?
@Bob__ Я не собираюсь делать это ответом, поскольку и GCC, и MSVC, похоже, не работают из-за синтаксиса такого решения. демо
@康桓瑋 Мне очень понравилась твоя идея. Я модифицировал его, чтобы он работал в gcc, msvc и icx (который раньше тоже не работал) здесь. Если вы хотите опубликовать его, пожалуйста, продолжайте. В противном случае я могу добавить эту альтернативу к моему ответу.
@TedLyngmo Не стесняйтесь добавлять альтернативы к своему ответу.
@康桓瑋 Спасибо за прекрасную идею.





Вы можете использовать std::array<double, N> в качестве параметра для функций
(вместо отдельных параметров double):
#include <array>
#include <functional>
template<int N>
class FunctionHolder {
public:
//--------------------------------------vvvvvvvvvvvvvvvvvvvvv--------
using function_type = std::function<int(std::array<double, N> args)>;
FunctionHolder(const function_type& arg) : m_func(arg) {}
private:
const function_type& m_func;
};
//----------vvvvvvvvvvvvvvvvvvvvv-------
int my_func(std::array<double, 2> arg2) {
return 0;
}
int main() {
FunctionHolder<2> my_holder(my_func);
}
Это решение накладывает на функцию другую подпись. Что, если я намерен использовать operator* как функцию? Мне пришлось бы использовать обертки вокруг него, что мешает читабельности
Я согласен. Но поскольку в вашем вопросе не упоминались такие детали, я подумал, что это направление может быть полезным.
Вы можете рекурсивно добавить аргументы функции в список шаблонов:
template <typename Ret, std::size_t N, typename Type, typename... T>
struct RepeatFunctionArgType {
static auto fnt() {
if constexpr (sizeof...(T) >= N) {
return std::function<Ret(T...)>{};
} else {
return RepeatFunctionArgType<Ret, N, Type, Type, T...>::fnt();
}
}
using type = decltype(fnt());
};
Доступ к этому можно получить как RepeatFunctionArgType<int,3,double>::type.
static int foo(double,double,double) {return 0;}
int main() {
RepeatFunctionArgType<int,3,double>::type fn = foo;
}
Как насчет этого:
#include <functional>
#include <type_traits>
template<int N, typename...Args>
struct FunctionHolder {
public:
using function_type = std::function<int(Args...)>;
static_assert(sizeof...(Args) == N);
static_assert((std::is_same_v<Args, double> && ...), "");
FunctionHolder(const function_type& arg): m_func(arg) {}
const function_type m_func;
};
template<int N, typename...Args>
auto makeFunctionHolder(int(*f)(Args...))
{
return FunctionHolder<N, Args...>(f);
}
int my_func(double arg1, double arg2)
{
return arg1+arg2;
}
int my_func2(double arg1, char arg2)
{
return arg1+arg2;
}
int my_func3(double arg1)
{
return arg1;
}
int main() {
auto h1 = makeFunctionHolder<2>(my_func);
//auto h2 = makeFunctionHolder<2>(my_func2); //should fail due to types
// auto h3 = makeFunctionHolder<2>(my_func3); //should fail to number
auto h3 = makeFunctionHolder<1>(my_func3); //should pass
return h1.m_func(4.0, 5.0);
}
https://godbolt.org/z/cjEq4s7Ef
Конечно, он использует фабричную функцию, но, возможно, этого можно добиться и с помощью руководства по дедукции. Все выложено только ради пробы, корректируйте по своему вкусу.
Дополнительная фабрика, которой следует избегать <N>: template<typename...Args>auto makeFunctionHolder(int(*f)(Args...)) { return FunctionHolder<sizeof...(Args), Args...>(f); }. Демо
Вы можете использовать лямбду в неопределенном контексте, чтобы получить возвращаемый тип:
using function_type =
decltype([]<std::size_t... I>(std::index_sequence<I...>) {
// expand to as many `double`s as there are `I`s:
return std::function<int(decltype((static_cast<void>(I)), double{})...)>{};
}(std::make_index_sequence<N>{}));
альтернативно
using function_type =
decltype([]<std::size_t... I>(std::index_sequence<I...>)
// expand to as many `double`s as there are `I`s:
-> std::function<int(decltype((static_cast<void>(I)), double{})...)> {
// this space intentionally left blank
}(std::make_index_sequence<N>{}));
Более общее решение, адаптированное из кода 康桓瑋 в комментариях, может выглядеть так:
template <std::size_t, class T>
using identity = T;
template <class R, class Arg, std::size_t N>
struct repeated_arg_func {
using type =
typename decltype([]<std::size_t... Is>(std::index_sequence<Is...>)
-> std::type_identity<R(identity<Is, Arg>...)> {
}(std::make_index_sequence<N>{}))::type;
};
template <class R, class Arg, std::size_t N>
using repeated_arg_func_t = typename repeated_arg_func<R, Arg, N>::type;
template <int N, class ArgType, class RetType>
class FunctionHolder {
public:
using signature = repeated_arg_func_t<RetType, ArgType, N>;
using function_type = std::function<signature>;
FunctionHolder(const function_type& arg) : m_func(arg) {}
private:
function_type m_func;
};
Примечание: член const function_type& — не очень хорошая идея, если вы на самом деле не предоставите std::function<...>, который переживет ваш FunctionHolder. Ваш пример создает временный std::function<...>, срок действия которого истечет, как только построение FunctionHolder будет завершено.
Я предлагаю сделать его обычным нереференсным членом:
function_type m_func;
Вот решение C++17, в котором вам не нужно явно специализировать держатель в main() или явно указывать количество аргументов.
#include <type_traits>
#include <functional>
#include <iostream>
// Some helpers to check if all arguments of the function have the same argument type
template<typename arg_t, typename... args_t>
static constexpr bool all_same_as_first()
{
return std::conjunction_v<std::is_same<arg_t, args_t>...>;
};
template<typename... args_t>
static constexpr bool all_same()
{
// Specialized logic for 0 or 1 argument, consider them the same.
if constexpr (sizeof...(args_t) < 2ul)
{
return true;
}
else
{
return all_same_as_first<args_t...>();
}
}
// The actual function holder
// it also needs specialization on the return value type
template<typename retval_t, typename... args_t>
class FunctionHolder
{
public:
static_assert(all_same<args_t...>()); // check condition that all arguments are of same type
// construct
explicit FunctionHolder(std::function<retval_t(args_t...)> fn) :
m_fn{ fn }
{
}
// allow calls to be made through the holder directly
retval_t operator()(args_t&&...args)
{
return m_fn(std::forward<args_t>(args)...);
}
private:
std::function<retval_t(args_t...)> m_fn;
};
// helper for type deduction of std::function type
template<typename fn_t>
auto make_holder(fn_t fn)
{
std::function std_fn{fn};
return FunctionHolder{ std_fn };
}
// your function
int my_func(double arg1, double arg2)
{
return static_cast<int>(arg1+arg2);
}
// And the usage
int main()
{
auto holder = make_holder(my_func);
auto retval = holder(1.0, 2.0);
return 0;
}
С шаблоном вспомогательного класса это может быть
template <std::size_t, typename T>
using always_t = T;
template<typename Seq> class FunctionHolderImpl;
template<std::size_t... Is>
class FunctionHolderImpl<std::index_sequence<Is...>> {
public:
using function_type = std::function<int(always_t<Is, double>... args)>;
FunctionHolder(const function_type& arg): m_func(arg) {}
private:
function_type m_func;
};
template <std::size_t N>
using FunctionHolder = FunctionHolderImpl<std::make_index_sequence<N>>;
Какую основную проблему вам необходимо решить? Как вы думаете, почему что-то вроде теоретического
std::repeated_typeрешит эту проблему? Если вместо этого вы спросите об этой проблеме напрямую, кто-то может предложить лучшее решение.