2

I want to make a std::map with std::string keys that contains functions. These functions should be templates in order to work with different numeric values:

template <typename T> T (*pfunc)(T,T);

The map declarations looks like this:

std::map<std::string, pfunc> funcMap;

I know I need a template argument list for pfunc but I don't really know how to do it.

[EDIT]

Taking in count the comments. I would like to be able to: Create a function template as:

template <typename T> 
T myMax(T x, T y) 
{ 
   return (x > y)? x: y; 
} 

or

template <typename T> 
T myMin(T x, T y) 
{ 
   return (x < y)? x: y; 
} 
  • Then add it to my map: funcMap["myMax"] = myMax; or funcMap["myMin"] = myMin;
  • And use it as:
funcMap["myMax"]<int>(3,4);
funcMap["myMax"]<float>(3.1,4.5);
funcMap["myMin"]<int>(3,4);
funcMap["myMin"]<float>(3.1,4.5);
  • Is this posible? any better ideas?
Ivan
  • 1,352
  • 2
  • 13
  • 31
  • Are you wanting one map with e.g. `pfunc` and `pfunc` mixed, or multiple maps? – Caleth Sep 02 '20 at 11:01
  • 6
    A template is only a type once it's instatiated. You can't have a map that holds a template. It need to be a `std::map> funcMap;` or similar. – super Sep 02 '20 at 11:01
  • The more relevant question here is how you are planning to use this. It's hard to point you in the right direction without seeing the use-case. I'm guessing you are planning to feed run-time string values into your map. You should provide more information as a [mcve]. – super Sep 02 '20 at 11:04
  • @Caleth I would like my map to be able to use any numeric values, and just choosing a type when calling the function inside the map – Ivan Sep 02 '20 at 11:07
  • 1
    Are you wanting to "put function templates in `funcMap`", or out functions of different signatures? The latter is [both hard to implement and hard to use correctly](https://stackoverflow.com/questions/45715219/store-functions-with-different-signatures-in-a-map/45718187#45718187) – Caleth Sep 02 '20 at 11:15
  • I.e. do you want something like `funcMap = { { "sine", std::sin }, { "sqrt", std::sqrt }, ... }` or `funcMap = { { "int_func", [](int a, int b){ return a + b;} }, { "double_func", [](double a, double b){ return a + b;} }, ... }`? – Caleth Sep 02 '20 at 11:18
  • @Caleth I want to put functions of different signatures AND I want to use templates (or something similar) so that the type is defined after the map declaration. I'm taking a look to the link you sent, it's similar to what I want, but I don't want to signatures with fixes types , thanks for you interest! – Ivan Sep 02 '20 at 11:22
  • 1
    Put in examples of calling these functions that you want. I really *don't* advise `AnyCallable`. You might be able to get away with having `funcMap` be a variable template: `template std::map> funcMap = { ... };` You would then call it like `int res = funcMap["some_func"](1, 2);` – Caleth Sep 02 '20 at 11:26
  • Note that: 1) When you call a function, the type of its arguments and its return type must be resolved at compile time. 2) Templates do not exist on runtime. They must be instantiated and you can work only with these instances. 3) There is nothing as a pointer to a function template. You can have a pointer to a function (which may happen to be a function template instance). – Daniel Langr Sep 02 '20 at 11:40
  • @Caleth I added to the question the way I want to call the function – Ivan Sep 02 '20 at 11:55
  • @Caleth when I do `template std::map> funcMap;` I get the error `Template argument for template type parameter must be a type` – Ivan Sep 02 '20 at 12:01
  • All the elements of a map must have the same type. A function template is not a function, does not have a type, and can't be stored anywhere. You can't instantiate a template at rutime. I think you should take a step back and think some more about your ultimate problem - whatever that is - instead of being stuck on this solution. – molbdnilo Sep 02 '20 at 12:05

3 Answers3

2

I think the best you will get is

template <typename T> 
T myMax(T x, T y) 
{ 
   return (x > y)? x: y; 
} 

template <typename T> 
T myMin(T x, T y) 
{ 
   return (x < y)? x: y; 
} 

template <typename T> using pfunc = T(*)(T, T);

template <typename T> std::map<std::string, pfunc<T>> funcMap = { 
    { "myMax", myMax }, 
    { "myMin", myMin } 
};

You will need to define all your function templates before defining funcMap, or limit yourself to a pre-determined set of types. I don't know of a way of populating infinite funcMap instantiations with infinite function template instantiations after it's definition.

Caleth
  • 52,200
  • 2
  • 44
  • 75
2

You can use a generic lambda:

[](auto x, auto y) { return myMax(x, y); }

However, this is not a function pointer, but rather an object.

Marshall Clow
  • 15,972
  • 2
  • 29
  • 45
0

As we don't know much about your final goal, we can't say if there is an easy workaround to your problem. When you are in front of a kind of unsolvable problem, you can follow the pragmatic programmer principle called "Cutting the Gordian Knot" by asking yourself:

  • Is there an easier way?
  • Am I solving the right problem?
  • Why is this a problem?
  • What makes it hard?
  • Do I have to do it this way?
  • Does it have to be done at all?

That's said, I think Caleth solution is the most straightforward. If you want a solution with a single map containing the whole overload set, here is a proof of concept (WARNING: It has many flaws and works only in your case).

First, you need some helpers to detect if a class has the right function call operator overload or not:

namespace helpers {
    // simplify is_detected pattern (see https://en.cppreference.com/w/cpp/experimental/is_detected)
    template <typename Dummy, template <typename...> typename Op, typename... Args>
    struct is_detected : std::false_type {};

    template <template <typename...> typename Op, typename... Args>
    struct is_detected<std::void_t<Op<Args...>>, Op, Args...> : std::true_type {};

    template <template <typename...> typename Op, typename... Args>
    constexpr bool is_detected_v = is_detected<void, Op, Args...>::value;

    // Check if a class has an overloaded function call operator with some params
    template <typename T, typename... Args>
    using has_fcall_t = decltype(std::declval<T>()(std::declval<Args>()...));

    template <typename T, typename... Args>
    constexpr bool has_fcall_v = is_detected_v<has_fcall_t, T, Args...>;
}

Then, you define your basic numerical operations:

template <typename T>
struct Additionner {
    T operator()(T a, T b) {
        return a + b;
    }
};

template <typename T>
struct Multiplier{
    T operator()(T a, T b) {
        return a * b;
    }
};

template <typename T>
struct Incrementor {
    T operator()(T a) {
        return a++;
    }
};

The next step is to gather all the specialization of an operation you are interested in in a single class:

// Used to store many overloads for the same operations
template <typename... Bases>
struct NumOverloader : Bases...
{
    using Bases::operator()...;
};

Internally, we use a std::variant to simulate a kind of heterogeneous map. We wrap it into a class to provide a straightforward interface to use:

// wrapper around a variant that expose a universal function call operator
template <typename... Ts>
class NumDispatcher {
public:
    NumDispatcher() = default;

    template <typename T> // Fine tuning needed (see https://mpark.github.io/programming/2014/06/07/beware-of-perfect-forwarding-constructors/)
    NumDispatcher(T&& t) : m_impl(std::forward<T>(t)){
    }

    // visit the variant
    template <typename... Args>
    auto operator()(Args... args) {
        using type = std::common_type_t<Args...>;
        type t{};
        std::visit([&](auto&& visited) {
            using vtype = std::decay_t<decltype(visited)>;
            if constexpr(helpers::has_fcall_v<vtype, Args...>)
                t = std::forward<vtype>(visited)(args...);
            else
                throw std::runtime_error("bad op args");
        }, m_impl);
        return t;
    }
private:
    using Impl = std::variant<Ts...>;
    Impl m_impl;
};

The final step is to define your mapping:

// Here you need to know at compile-time your overloads
using MyIncrementors = NumOverloader<Incrementor<int>, Incrementor<unsigned>>;
using MyAdditionners = NumOverloader<Additionner<int>, Additionner<double>>;
using MyMultipliers = NumOverloader<Multiplier<int>, Multiplier<double>>;

using MyValueType = NumDispatcher<MyIncrementors, MyAdditionners, MyMultipliers>;
using MyMap = std::map<std::string, MyValueType>;

And then, you can play with it:

Num::MyMap m;
m["add"] = Num::MyAdditionners{};
m["mul"] = Num::MyMultipliers{};
m["inc"] = Num::MyIncrementors{};

auto d = m["add"](2.4, 3.4);
std::cout << d << std::endl;
// auto d2 = m["add"](1.3f, 2); // throw no overload match
std::cout << m["inc"](1) << std::endl;
//std::cout << m["inc"](1,1) << std::endl; // throw no overload match
std::cout << m["mul"](3, 2) << std::endl;

DEMO HERE.

Regards.

nop666
  • 585
  • 2
  • 6