简体   繁体   English

重载可变参数模板的固定参数

[英]Overload variadic template's fixed parameters

Is it possible to override a variadic template by changing the number of fixed parameters before the function parameter pack? 是否可以通过在函数参数包之前更改固定参数的数量来覆盖可变参数模板? For example: 例如:

#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 to be called, but I want the first to be called. 运行它会导致第二个foo被调用,但我希望第一个被调用。 Is there a better way to overload this function? 有没有更好的方法来重载此功能?

Second variant is selected because it does not involve an extra conversion required to create an instance of std::string for the last argument. 选择第二个变体是因为它不涉及为最后一个参数创建std::string实例所需的额外转换。 If you call class constructors explicitly (or adjust arguments to take 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 在线编译器

This is because a string literal is not of type std::string . 这是因为字符串文字不是std::string类型。 It is of type const char[N] . 它的类型为const char[N] So the type of the third parameter of the function instantiated from the second function template is deduced to be const char* , which is considered a better match than the function instantiated from the first function template. 因此,从第二个函数模板实例化的函数的第三个参数的类型被推导为const char* ,这被认为是比从第一个函数模板实例化的函数更好的匹配。

You can change the type of the parameters from std::string to const char* , or use explicit conversions suggested by VTT's answer. 您可以将参数类型从std::string更改为const char* ,或使用VTT答案建议的显式转换。

As explained by VTT and xskxzr, "data" is a string literal, so is a const char [5] , so is convertible to but not exactly a std::string , so a generic template type is a better match than a std::string and the compiler prefer the first version. 正如VTT和xskxzr所解释的那样, "data"是一个字符串文字,因此是一个const char [5] ,所以可以转换为但不完全是std::string ,因此通用模板类型比std::string更好匹配std::string和编译器更喜欢第一个版本。

You can choose the first version of foo() passing a std::string{"data"} but, if you don't want to change the call, another possible solution is SFINAE enable/disable the second version of foo() . 您可以选择传递std::string{"data"}foo()的第一个版本,但是,如果您不想更改调用,另一个可能的解决方案是SFINAE启用/禁用第二个版本的foo()

I mean... if you write a fIsCToS (as "first is convertible to string") custom type traits as follows 我的意思是...如果你写一个fIsCToS (作为“首先是可转换为字符串”)自定义类型特征如下

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 您可以使用它重写第二个版本的foo() ,如下所示

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

The following 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 如果你可以使用C ++ 14,你可以使用std::enable_if_t而不是typename std::enable_it<...>::type这样第二个foo()可以简化为

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

OK, so I came up with a different solution. 好的,所以我想出了一个不同的解决方案。 I hate answering my own question, but this is sort of the answer I was looking for and maybe it can help others. 我讨厌回答我自己的问题,但这是我正在寻找的答案,也许它可以帮助别人。

What I did instead was to not overload the templated function and instead combine the entire signature into a single parameter pack. 我所做的是不重载模板化函数,而是将整个签名组合到一个参数包中。 Then, I overloaded a non-templated function that does the actual work, using std::forward to forward the args to one of these overloaded functions. 然后,我重载了一个执行实际工作的非模板化函数,使用std::forward forward将args转发给其中一个重载函数。 In this way, I let the compiler decide which method to call. 这样,我让编译器决定调用哪个方法。

My solution: 我的解决方案

#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 it running here : 看到它在这里运行:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM