1

I am writing a C++ network library and would like the main (template) function to accept parameters in random order, to make it more user friendly, in the same way the CPR library does.

The template function will accept up to 10 parameters at the same time, each a different type. Is there a way to instantiate the template to accept any random order of param types, other than having to manually include code for every possibility?

For example - in this case using 3 params each a different type:

.h file

namespace foo
{
    template <typename T, typename U, typename V> void do(const T& param_a, const U& param_b , const V& param_c);
};

.cpp file

template <typename T, typename U, typename V>
void foo::do(const T& param_a, const U& param_b, const V& param_c) {
//do lots of stuff
}

//instantiate to allow random param order 
template void foo::do<int, std::string, long>(const int&, const std::string&, const long&);
template void foo::do<int, long, std::string>(const int&, const long&, const std::string&);
template void foo::do<int, std::string, int>(const int&, const std::string&, const int&);
//etc... to cover all possible param orders
hack-tramp
  • 366
  • 3
  • 11
  • How is `string` different from `string`? See second instantiation. – zdf Apr 14 '21 at 10:26
  • 2
    You might be interested by [how-to-generate-all-the-permutations-of-function-overloads](https://stackoverflow.com/a/30561530/2684539) (C++14 actually, but might be done in C++11). – Jarod42 Apr 14 '21 at 10:28
  • @zdf my bad, fixed it to a different type – hack-tramp Apr 14 '21 at 10:30
  • @Yksisarvinen it is a library, I need to instantiate so the app will link – hack-tramp Apr 14 '21 at 10:32
  • Explicit instantiations has to be done manually (Possibly with Macro helper). You might have template code for implicit instantiations... but implicit, so not explicit... – Jarod42 Apr 14 '21 at 13:27
  • 2
    BTW, you don't need to specify template arguments inside `<>` as there are deducible. – Jarod42 Apr 14 '21 at 13:28

4 Answers4

3

If your goal is to match the API design of a given library, best way to learn is to dig into its source code and dissect it.

Consider this snippet of code (I'm still using CPR as an example as you mentionned it as a reference):

cpr::Session session;
session.SetOption(option1);
session.SetOption(option2);
session.SetOption(option3);

You want a method which can handle option1, option2, ... , no matter in which order they are provided. The subsequent calls to SetOption could be replaced with a single SetOptions(option3, option1, option2). Therefore we need a variadic SetOptions method:

template<typename Ts...> // important: don't specialize the possible argument types here
void SetOptions(Ts&&... ts)
{ /* do something for each param in ts... */ }

The question is "how do you call SetOption for each item inside the ts parameter-pack ?". This is a mission for std::initializer_list. You can find a simple example here.

The key here is to have an overloaded function which can handle each argument type separately (example in CPR with SetOptions). Then, inside your "permutable" function, you call the overloaded function for each of your arguments, one at a time (example in CPR, which is then used in various places).

One thing to note though is that you can pass multiple parameters of the same type. Depending on what you want to achieve, this can be an issue or not.

Also, you can call the method with unsupported argument types (matching none of your overloads), in this case the error message is not always explicit depending on which compiler you are using. This is — however — something you could overcome using static_asserts.

abidon
  • 456
  • 4
  • 15
  • 1
    This is brilliant - took into account every aspect of my question. Thank you! – hack-tramp Apr 14 '21 at 20:19
  • How to instantiate so the app will link? – hack-tramp Apr 20 '21 at 14:34
  • Consider you have a template like this one: `template foo(T arg) { bar(arg); }`. If in your code you call `foo` with either an `int` or a `std::string`, you'll need to have two overloads of `bar` defined: one accepting an `int` (or any type that can be implicitly converted from an `int`) and a `std::string` (or any type that can implicitly converts). If you try to call `foo` with any other type — let's say `std::vector` — and bar has no overload accepting a `std::vector`, then you have an issue. You'll need to define an new overload of bar to fix it. – abidon Apr 21 '21 at 15:32
1

Is there a way to instantiate the template to accept any random order of param types, other than having to manually include code for every possibility?

You cannot do this for explicit instantiation definitions without macros, but you could use a separate approach and rely on implicit instantiations instead, using SFINAE to restrict the primary template (whose definition you move to the header file) based on two custom traits.

To begin with, given the following type sequence

template <class... Ts>
struct seq {};

we want to construct a trait that, for a given type sequence seq<T1, T2, ...> (your "10 parameter types"), denoted as s:

  • s shall be a subset of set of types of your choosing seq<AllowedType1, ...>, and
  • s shall contain only unique types.

We can implement the former as:

#include <type_traits>

template <class T, typename... Others>
constexpr bool is_same_as_any_v{(std::is_same_v<T, Others> || ...)};

template <typename, typename> struct is_subset_of;

template <typename... Ts, typename... Us>
struct is_subset_of<seq<Ts...>, seq<Us...>> {
  static constexpr bool value{(is_same_as_any_v<Ts, Us...> && ...)};
};

template <typename T, typename U>
constexpr bool is_subset_of_v{is_subset_of<T, U>::value};

and the latter as

template <typename...> struct args_are_unique;

template <typename T> struct args_are_unique<T> {
  static constexpr bool value{true};
};

template <typename T, typename... Ts> struct args_are_unique<seq<T, Ts...>> {
  static constexpr bool value{!is_same_as_any_v<T, Ts...> &&
                              args_are_unique<seq<Ts...>>::value};
};

template <typename... Ts>
constexpr bool args_are_unique_v{args_are_unique<Ts...>::value};

after which we can define the primary template as

namespace foo {
namespace detail {
using MyAllowedTypeSeq = seq<int, long, std::string>;  // ...
} // namespace detail

template <
    typename T, typename U, typename V, typename Seq = seq<T, U, V>,
    typename = std::enable_if_t<is_subset_of_v<Seq, detail::MyAllowedTypeSeq> &&
                                args_are_unique_v<Seq>>>
void doStuff(const T &param_a, const U &param_b, const V &param_c) {
  // do lots of stuff
}
} // namespace foo

and where we may and may not use the primary template overload as follows:

int main() {
  std::string s{"foo"};
  int i{42};
  long l{84};
  foo::doStuff(s, i, l); // OK
  foo::doStuff(s, l, i); // OK
  foo::doStuff(l, i, s); // OK
  foo::doStuff(l, s, i); // OK

  // uniqueness
  foo::doStuff(l, l, i); // Error: candidate template ignored

  // wrong type
  unsigned int ui{13};
  foo::doStuff(s, ui, l); // Error: candidate template ignored
}

If types need not actually be unique (it's somewhat unclear from the question) you can simply SFINAE-constrain the primary template only on the first is_subset_of_v trait:

template <
    typename T, typename U, typename V, typename Seq = seq<T, U, V>,
    typename = std::enable_if_t<is_subset_of_v<Seq, detail::MyAllowedTypeSeq>>>
void do(const T &param_a, const U &param_b, const V &param_c) {
  // do lots of stuff
}
dfrib
  • 70,367
  • 12
  • 127
  • 192
0

Why not use the builder pattern here? You would create a foo_builder with various setXxx methods and a final build() to get the fully configured object.

Tanveer Badar
  • 5,438
  • 2
  • 27
  • 32
-1

Use a struct to hold all the params.

namespace foo
{
    struct do_params {
        int a;
        long b;
        std::string c;
    };
    void do(do_params params);
};
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • this does not explain how to allow for random parameter order (not possible anyway with that code) or how to link in the context of a library – hack-tramp Apr 21 '21 at 13:21