【问题标题】:Call function template overload for derived variadic class template为派生的可变参数类模板调用函数模板重载
【发布时间】:2013-02-03 22:54:03
【问题描述】:

我有一个可变参数类模板deriv,它派生自可变参数类模板base

我有一个函数模板,它接受任何类型T,以及base<Ts...> 类型的重载;

如何在传递const deriv<Ts...>& 时使用base<Ts...> 重载?

下面的工作示例:

#include <iostream>
#include <tuple>

template<typename... Ts>
struct base
{
    std::tuple<Ts...> tuple;
};

template<typename... Ts>
struct deriv : base<Ts...>
{
};

//--------------------------------

template<typename T>
void func(const T&)
{
    std::cout << "T" << std::endl;
}

template<typename... Ts>
void func(const base<Ts...>&)
{
    std::cout << "base<Ts...>" << std::endl;
}

//----------------------------------------

int main()
{
    int a;
    base <int, double> b;
    deriv<int, double> c;

    func(a);
    func(b);
    func(c); // <--- I want func<base<Ts...>> not func<T> to be called here

    exit(0);
}

示例输出:

T
base<Ts...>
T

我想要的输出是什么:

T
base<Ts...>
base<Ts...>

【问题讨论】:

  • 有趣的问题,我花了几分钟摆弄,不幸的是没有任何结果。希望比我更好的程序员能回答。

标签: c++ templates c++11


【解决方案1】:

除非你准备好重新设计你的代码,你不能,并且有充分的理由。

func() 的非可变参数重载比可变参数版本更好地匹配:事实上,在尝试解析函数调用时,类型参数 T 用于非可变参数过载将被推断为derived&lt;int, double&gt;

另一方面,可变参数重载中的参数包Ts 将被推导出为int, double。在类型推导之后,这实际上会给编译器留下这两种选择来解决您的调用:

void func(const deriv<int, double>&); // Non-variadic after type deduction
void func(const base<int, double>&);    // Variadic after type deduction

在尝试匹配参数类型为derived&lt;int, double&gt; 的调用时应该选择哪一个?

deriv<int, double> c;
func(c);

显然,第一个非可变参数重载是更好的匹配

那么,如何调用 second 重载而不是第一个?你有几个选择。首先,您可以通过显式指定模板参数来限定您的调用:

func<int, double>(c);

如果您不喜欢这样,也许您可​​以重新考虑func() 的非可变重载的定义:您真的希望它接受任何 可能的类型T?或者是否有某些类型您知道不应调用此重载?如果是这样,您可以使用 SFINAE 技术和std::enable_if 来排除不需要的匹配。

作为进一步的可能性,您可以稍微放宽模板函数的签名,并允许将其参数推断为某个模板类的实例化:

template<template<typename...> class T, typename... Ts>
void func(const T<Ts...>&)
{
    std::cout << "base<Ts...>" << std::endl;
}

仅此更改就应该以您想要的方式修复程序的行为。

更新:

如果您希望为派生自 base&lt;&gt; 类模板的任何实例的类调用专用函数模板,则可以在以下方式:

template<template<typename...> class T, typename... Ts>
void func(
    const T<Ts...>&, 
    typename std::enable_if<
        std::is_base_of<base<Ts...>, T<Ts...>>::value
        >::type* = nullptr
    )
{
    std::cout << "base<Ts...>" << std::endl;
}

附录:

在模板函数重载对您的设计没有帮助的情况下,请注意您始终可以求助于部分模板专业化。不幸的是,函数模板不能被特化,但您仍然可以利用 class 模板的部分特化并添加一个辅助函数来隐藏该模板的实例化。这就是你重写代码的方式:

namespace detail
{
    template<typename T>
    struct X
    {
        static void func(const T&)
        {
            std::cout << "T" << std::endl;
        }
    };

    template<template<typename...> class T, typename... Ts>
    struct X<T<Ts...>>
    {
        static void func(const T<Ts...>&)
        {
            std::cout << "base<Ts...>" << std::endl;
        }
    };
}

template<typename T>
void func(const T& t)
{
    details::X<T>::func(t);
}

【讨论】:

  • 我发现您写得很好的答案很直观,并感谢您花费的时间。
  • @Iori:其实我还要写一个解决方案,请稍等。
  • @AndyProwl - 第二个struct X 是否可以只匹配base&lt;Ts...&gt; 对象和base&lt;Ts...&gt; 派生对象?即:如果我通过std::tuple&lt;Ts...&gt;(它目前确实接受),它应该无法捕获
  • @Iori:你去吧,我更新了我的答案——实际上,我并不是建议你不接受它:-D
  • @Iori:是的,通过使用一些 SFINAE 机制和 std::is_base_of 特征是可能的。但是,这会变得有点复杂。
【解决方案2】:

通用模板重载是更好的匹配,因为它不需要转换(除了添加const,这两个重载都有)。

您可以通过添加显式强制转换 (example) 来获得 base-template 重载:

func(static_cast<base<int, double> &>(c));

(或者,您可以放弃多个重载,而是将一些 is_base_of 辅助逻辑粘贴到主函数模板的主体中。)

【讨论】:

  • func 的调用在另一个函数模板中,所以我现在不知道c 的类型。因此,我需要模板实例化隐式工作。
  • @SethCarnegie:您将参数包添加到模板中。我将在一分钟内展示,请稍等
  • @AndyProwl 是的,我刚刚看到你的回答,我不知道这是可能的 :)
猜你喜欢
  • 2021-10-01
  • 2014-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-23
  • 2021-03-19
相关资源
最近更新 更多