Как объявить функцию с N аргументами типа double?

Я хотел бы иметь шаблон класса, способный хранить функциональный объект, принимающий ровно «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::repeated_type решит эту проблему? Если вместо этого вы спросите об этой проблеме напрямую, кто-то может предложить лучшее решение.

Some programmer dude 31.07.2023 10:00

Как насчет использования std::array<double, N> в качестве параметра?

wohlstad 31.07.2023 10:05

Двойные аргументы «N» — это std::array<double, N> или std::initializer_list<double>.

273K 31.07.2023 10:05

Отправить массив или указатель на структуру.

i486 31.07.2023 10:17

Похоже, вам нужно godbolt.org/z/j9vchqxbx

康桓瑋 31.07.2023 10:29

@康桓瑋 Это действительно похоже на то, что хотел OP (я бы просто сделал это немного более общим). Не могли бы вы расширить свой комментарий в ответ?

Bob__ 31.07.2023 10:46

@Bob__ Я не собираюсь делать это ответом, поскольку и GCC, и MSVC, похоже, не работают из-за синтаксиса такого решения. демо

康桓瑋 31.07.2023 10:49

@康桓瑋 Мне очень понравилась твоя идея. Я модифицировал его, чтобы он работал в gcc, msvc и icx (который раньше тоже не работал) здесь. Если вы хотите опубликовать его, пожалуйста, продолжайте. В противном случае я могу добавить эту альтернативу к моему ответу.

Ted Lyngmo 31.07.2023 13:00

@TedLyngmo Не стесняйтесь добавлять альтернативы к своему ответу.

康桓瑋 31.07.2023 13:03

@康桓瑋 Спасибо за прекрасную идею.

Ted Lyngmo 31.07.2023 13:04
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
10
237
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Вы можете использовать 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* как функцию? Мне пришлось бы использовать обертки вокруг него, что мешает читабельности

Adam Ryczkowski 31.07.2023 10:43

Я согласен. Но поскольку в вашем вопросе не упоминались такие детали, я подумал, что это направление может быть полезным.

wohlstad 31.07.2023 10:52
Ответ принят как подходящий

Вы можете рекурсивно добавить аргументы функции в список шаблонов:

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); }. Демо

Jarod42 31.07.2023 12:46

Вы можете использовать лямбду в неопределенном контексте, чтобы получить возвращаемый тип:

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

Другие вопросы по теме