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

时间:2018-02-23 13:05:23

标签: c++ c++14 variadic-templates variadic-functions

是否可以通过在函数参数包之前更改固定参数的数量来覆盖可变参数模板?例如:

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

运行它会导致第二个foo被调用,但我希望第一个被调用。是否有更好的方法来重载此功能?

4 个答案:

答案 0 :(得分:6)

选择第二个变体是因为它不涉及为最后一个参数创建std::string实例所需的额外转换。如果你明确地调用类构造函数(或调整参数以完全接受你传递的),那么它将正常工作:

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

online compiler

答案 1 :(得分:4)

这是因为字符串文字不是$Folders = @( 'C:\Users\PAT\Desktop\attachement testfolder' ) $DestFolder = 'C:\Users\PAT\Desktop\Backup folder' $EmailList = @( 'recieving-email@hotmail.com' ) $From = 'pat@email.com' $Subject = 'New files' $SMTPServer = '111.111.111.111' # Get new files $NewFiles = $Folders | Get-ChildItem -File -Recurse # If there are an new files... If ( $NewFiles ) { # Add the file count and file names to the body of the email $Body = '$($NewFiles.Count) new files attached' $NewFiles | ForEach { $Body += $_.Name + "" } # Send the email with attachments Send-MailMessage -From $From -To $EmailList -Subject $Subject -Body $Body - BodyAsHTML -Attachments $NewFiles.FullName -SmtpServer $SMTPServer # Move the emailed files to the destination folder $NewFiles.FullName | Move-Item -Destination $DestFolder -Force } 类型。它的类型为this.$validator.localize('fr', french) 。因此,从第二个函数模板实例化的函数的第三个参数的类型推导为std::string,这被认为是比从第一个函数模板实例化的函数更好的匹配。

您可以将参数类型从const char[N]更改为const char*,或使用VTT答案建议的显式转化。

答案 2 :(得分:0)

正如VTT和xskxzr所解释的那样,"data"是一个字符串文字,因此是const char [5],所以可以转换为但不完全是std::string,因此通用模板类型是比std::string更好匹配,编译器更喜欢第一个版本。

您可以选择通过foo()的{​​{1}}的第一个版本,但是,如果您不想更改通话,另一个可能的解决方案是SFINAE启用/禁用{{1的第二个版本}}

我的意思是......如果你写一个std::string{"data"}(作为“第一个可转换成字符串”)自定义类型特征如下

foo()

您可以使用它重写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>
 { };

以下是您修改过的示例

foo()

如果您可以使用C ++ 14,则可以使用template <typename ...Args> typename std::enable_if<false == fIsCToS<Args...>{}>::type foo(std::string, std::string, Args...) { std::cout << "TWO STRINGS\n"; } 代替#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 } ,以便第二个std::enable_if_t可以简化为

typename std::enable_it<...>::type

答案 3 :(得分:-1)

好的,所以我想出了一个不同的解决方案。我讨厌回答我自己的问题,但这是我正在寻找的答案,也许它可以帮助别人。

我所做的是不重载模板化函数,而是将整个签名组合到一个参数包中。然后,我重载了一个执行实际工作的非模板化函数,使用std::forward将args转发给其中一个重载函数。通过这种方式,我让编译器决定调用哪个方法。

我的解决方案:

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

看到它正在运行here