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 
6
  • How is string different from string? See second instantiation. Commented Apr 14, 2021 at 10:26
  • 2
    You might be interested by how-to-generate-all-the-permutations-of-function-overloads (C++14 actually, but might be done in C++11). Commented Apr 14, 2021 at 10:28
  • @zdf my bad, fixed it to a different type Commented Apr 14, 2021 at 10:30
  • @Yksisarvinen it is a library, I need to instantiate so the app will link Commented Apr 14, 2021 at 10:32
  • 2
    BTW, you don't need to specify template arguments inside <> as there are deducible. Commented Apr 14, 2021 at 13:28

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

5 Comments

This is brilliant - took into account every aspect of my question. Thank you!
How to instantiate so the app will link?
Consider you have a template like this one: template <typename T> 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<int> — and bar has no overload accepting a std::vector<int>, then you have an issue. You'll need to define an new overload of bar to fix it.
One of the links (cpp.sh/74jo2) is down.
Update: Nevermind, the example in CPR source-code using a fold expression made things much clearer (this one: (session.SetOption(std::forward<Ts>(ts)), 0)...)
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 } 

Comments

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.

Comments

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

1 Comment

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.