标签调度,可变参数模板,通用引用和错过的const说明符

时间:2016-01-12 11:39:44

标签: c++ c++11 const variadic-templates

请考虑以下示例(标签调度,可变参数模板,完美转发等等):

#include <iostream>
#include <utility>
#include <string>

struct A { };
struct B { };

void doIt(A&&, const std::string &) {
    std::cout << "A-spec" << std::endl;
}

template<typename T, typename... Args>
void doIt(T&&, Args&&...) {
    std::cout << "template" << std::endl;
}

template<typename T, typename... Args>
void fn(Args&&... args) {
    doIt(T{}, std::forward<Args>(args)...);
}

int main() {
    const std::string foo = "foo";
    std::string bar = "bar";
    fn<A>(foo);
    fn<A>(bar);
    fn<B>(foo);
}

在这种情况下,输出为:

A-spec
template
template

原因很明显,它并没有打扰我。

我想要实现的是在两种情况下调用doIt函数的第一个实例,无论字符串是否具有const说明符。
当然,一个可能的解决方案是使用正确的原型定义一个新的doIt,无论如何我想知道是否还有其他解决方案。

到目前为止,我试图通过add_const来获取它,但我很确定我错过了什么。
是否有任何可行的解决方案可以静默添加const说明符并使其正常工作?

修改

我已经更新了上面的例子,以便更加符合真正的问题 此外,尽管有趣的答案,我忘了引用这只是一个简化的例子,所以真正的问题不仅涉及std::string。相反,它可能发生(作为示例)标记A参数为intconst std::string &,而对于标记B,参数为float,类C的实例,依此类推。
因此,试图以某种方式使用std::string类型解决问题的那些答案无法解决真正的问题,对不起。

3 个答案:

答案 0 :(得分:5)

引入两个独立的函数,以便它们不会相互冲突,编译器不会引起任何歧义错误:

void doIt(A&&, const std::string &)
{
    std::cout << "A-spec" << std::endl;
}

template <typename T, typename... Args>
void doIt_template(T&&, Args&&...)
{
    std::cout << "template" << std::endl;
}

优先考虑两个额外的重载;首选的是尝试调用目标函数的专用非模板化版本:

template <typename T, typename... Args>
auto fn_impl(int, Args&&... args)
    -> decltype(doIt(T{}, std::forward<Args>(args)...), void())
{
    doIt(T{}, std::forward<Args>(args)...);
}

template <typename T, typename... Args>
void fn_impl(char, Args&&... args)
{
    doIt_template(T{}, std::forward<Args>(args)...);
}

介绍一位通用调度员:

template <typename T, typename... Args>
void fn(Args&&... args)
{
    fn_impl<T>(0, std::forward<Args>(args)...);
}

DEMO

答案 1 :(得分:4)

转发引用的重载很痛苦,因为它们非常非常贪婪。

如果Args...是可转换为std::string的单个参数,则可以选择禁用模板版本:

template<typename... Args>
struct convertible_to_single_string {
    static std::true_type help (std::string);
    static std::false_type help (...);
    using type = decltype(help(std::declval<Args>()...));
    static constexpr auto value = type::value;
};

template<typename... Args>
std::enable_if_t<!convertible_to_single_string<Args...>::value, void> 
doIt(int, Args&&...) {
    std::cout << "template" << std::endl;
}

这也会调用const char*的字符串版本或用户定义转换为std::string的任何内容。如果您不希望这样,还有其他可能性,例如从类型中删除所有引用和cv限定符并检查它是否为std::is_same<std::string, T>

Live Demo

答案 2 :(得分:1)

template<class String,
  std::enable_if_t<std::is_same<std::decay_t<String>,std::string>{},int> =0
>
void doIt(int, String&&) {
  std::cout << "std::string" << std::endl;
}

这使用转发引用,然后SFINAE只在其中获取任何string类型。

如果你愿意将你的args捆绑成一个元组,你也可以这样做usimg标签调度。可以使用is_same替换is_convertible,然后可以使用实际转换为std::string的具有不同名称的辅助函数。

另一种方法是在第二层中为标记保留第一个arg,然后手动计算出你想要的重载,并生成该标记类型。