Variable variables template fixed parameters

Is it possible to override the variational pattern by changing the number of fixed parameters before the function parameter package? For instance:

#include <iostream> template <typename ...Args> void foo(std::string, std::string, std::string, Args...) { std::cout << "THREE STRINGS\n"; } template <typename ...Args> void foo(std::string, std::string, Args...) { std::cout << "TWO STRINGS\n"; } int main() { foo("key", "msg", "data", 1); } 

Running this causes the second foo be called, but I want the first to be called. Is there a better way to overload this feature?

+5
source share
4 answers

The second option is chosen because it does not require the additional conversion necessary to create an instance of std::string for the last argument. If you explicitly call class constructors (or tweak the arguments to accept exactly what you pass), then it will work fine:

 foo(std::string{"key"}, std::string{"msg"}, std::string{"data"}, 1, 2); // THREE STRINGS 

online compiler

+6
source

This is because a string literal is not of type std::string . It has type const char[N] . Thus, the type of the third parameter of the function created from the second function template is displayed as const char* , which is considered a better match than the function created from the first function template.

You can change the parameter type from std::string to const char* or use the explicit conversions suggested by VTT's answer.

+4
source

As explained by VTT and xskxzr, "data" is a string literal, so it is const char [5] , so it is converted to, but not exactly, to std::string , so the general type of the template is better compared to std::string and the compiler prefers the first version.

You can select the first version of foo() passing std::string{"data"} , but if you do not want to change the call, another possible solution is to enable / disable the second version of foo() SFINAE.

I mean ... if you write fIsCToS (as "the first option to convert to string"), as shown below

 template <typename...> // for empty `Args...` list case struct fIsCToS : std::false_type { }; template <typename T0, typename ... Ts> struct fIsCToS<T0, Ts...> : std::is_convertible<T0, std::string> { }; 

you can rewrite the second version of foo() using it as follows

 template <typename ...Args> typename std::enable_if<false == fIsCToS<Args...>{}>::type foo(std::string, std::string, Args...) { std::cout << "TWO STRINGS\n"; } 

Below is your modified example

 #include <iostream> template <typename...> struct fIsCToS : std::false_type { }; template <typename T0, typename ... Ts> struct fIsCToS<T0, Ts...> : std::is_convertible<T0, std::string> { }; template <typename ...Args> void foo(std::string, std::string, std::string, Args...) { std::cout << "THREE STRINGS\n"; } template <typename ...Args> typename std::enable_if<false == fIsCToS<Args...>{}>::type foo(std::string, std::string, Args...) { std::cout << "TWO STRINGS\n"; } int main () { foo("key", "msg", "data", 1, 2); // now print THREE STRINGS } 

If you can use C ++ 14, you can use std::enable_if_t instead of typename std::enable_it<...>::type , so the second foo() can be simplified as

 template <typename ...Args> std::enable_if_t<false == fIsCToS<Args...>{}> foo(std::string, std::string, Args...) { std::cout << "TWO STRINGS\n"; } 
0
source

OK, so I came up with a different solution. I hate answering my question, but this is the kind of answer I was looking for, and maybe it can help others.

Instead, I had to not overload the template function and instead combine the entire signature into a single parameter package. Then I overloaded the non-templated function, which does the actual work, using std::forward to redirect the arguments to one of these overloaded functions. That way, I let the compiler decide which method to call.

My decision:

 #include <iostream> void print_call(std::string key, std::string msg, std::string data, int) { std::cout << "THREE STRINGS\n"; } void print_call(std::string key, std::string msg, int) { std::cout << "TWO STRINGS\n"; } template <typename ...Args> void foo(Args &&...args) { print_call(std::forward<Args>(args)...); } int main() { foo("key", "msg", 1); foo("key", "msg", "data", 1); } 

See how it works here :

-1
source

Source: https://habr.com/ru/post/1275535/


All Articles