16

我正在尝试创建一个可变参数模板类,它为类型列表中的每个类提供一个方法。下面显示了一个示例,它print为类型列表中的每个类创建一个方法:

#include <iostream>
#include <string>

// Helper class providing a function call
template <typename T>
class PrintHelper
{
public:
    void print(const T& t) { std::cout << t << std::endl; }
};

// Provides a print method for each type listed
template <typename... Ts>
class Printer : public PrintHelper<Ts>...
{};

int main()
{
    Printer<int, std::string> p;
    p.print(std::string("Hello World")); // Ambiguous Call
}

注释行导致 GCC 4.6.3 在注释行上出现错误。解决歧义的正确方法是什么,或者我应该寻找不同的设计?

4

3 回答 3

10

为了解决歧义,可以这样做

template <typename... Ts>
struct Printer : PrintHelper<Ts>...
{
    template <typename U>
    void print (const U& t)
    {
        PrintHelper<U>::print (t);
    }
};

(见一个例子

但这并不像人们希望的那样强大。特别是,您不能打印可转换为类型列表中的一种类型的对象。

然而,通过一些模板元编程,可以发送到正确的打印机。为此,您必须选择一种可转换的类型Ts...U然后调用 right PrintHelper,即。

PrintHelper<typename find_convertible<U, Ts...>::type>::print (t);

其中find_convertible<U, Ts...>定义为

template <typename U, typename... Ts>
struct find_convertible
{};

template <typename U, typename V, typename... Ts>
struct find_convertible<U, V, Ts...> :
    std::conditional<
        std::is_convertible<U, V>::value, 
        std::common_type<V>, // Aka identity
        find_convertible<U, Ts...>
    >::type
{};

(见例子

于 2012-12-13T17:34:42.827 回答
8

我不喜欢回答我自己的问题,但我从这里的评论中创建了以下解决方案。它将所有print函数纳入范围,并允许对所有函数进行 C++ 重载解析。

#include <iostream>
#include <string>

// Helper class providing a function call
template <typename T>
class PrintHelper
{
public:
    void print(const T& t) { std::cout << t << std::endl; }
};

// Provides a print method for each type listed
template <typename... Ts>
class Printer
{};

template<typename T>
class Printer<T> : public PrintHelper<T>
{
public:
    using PrintHelper<T>::print;
};

template<typename T, typename... Ts>
class Printer<T, Ts...>: public PrintHelper<T>, public Printer<Ts...>
{
public:
    using PrintHelper<T>::print;
    using Printer<Ts...>::print;
};

int main()
{
    Printer<int, std::string> p;
    p.print("Hello World"); // Not an ambiguous Call
}
于 2012-12-13T21:10:22.257 回答
3

以下代码可以解决歧义问题:

#include <iostream>
#include <string>

// Helper class providing a function call
template <typename T>
class PrintHelper
{
  protected:
    void print_impl(const T& t) { std::cout << t << std::endl; }
};

// Provides a print method for each type listed
template <typename... Ts>
class Printer : public PrintHelper<Ts>...
{
  public:
    template <typename U>
    void print(const U& u) { 
      PrintHelper<U>::print_impl(u); 
    };
};

int main()
{
    Printer<int, std::string> p;
    p.print(std::string("Hello World")); // Ambiguous Call
}

这不是很好,因为 U 类型(根据调用推断)恰好是可变参数类型列表中的类型之一。你也许可以花点心思来解决这个问题。使用一点模板魔法和 Sfinae,您可能可以相当轻松地解决这个问题(但肯定没有那么整洁)。

歧义问题与模板或可变参数模板的使用无关,当然,它是成员查找规则(标准的第 10.2/2 节)的直接应用,即通常所说的“成员隐藏规则” ”。如果你采用这个更简单的非模板版本,你会遇到同样的歧义问题,但有一个非常简单的解决方案:

struct IntPrinter {
  void print(const int& i) { std::cout << i << std::endl; };
};

struct StringPrinter {
  void print(const std::string& s) { std::cout << s << std::endl; };
};

struct IntStringPrinter : IntPrinter, StringPrinter {
  using IntPrinter::print;       // These using-statements will solve the problem
  using StringPrinter::print;    // by importing all 'print' functions to the same 
                                 // overload resolution level.
};

所以,这里的问题实际上是在编译器甚至尝试应用正常的重载解析规则之前就出现了歧义,因为它首先尝试找出要遵循继承的哪个分支来查找成员函数,然后,它将仅在一个继承级别解决重载。使用可变参数模板时的问题是,似乎没有任何方法可以解压缩一组“使用”语句来将所有打印函数导入到相同的继承级别。如果有人知道解压此类使用语句的方法,我会全神贯注。您可能不得不回退到预可变模板解决方案(例如具有 10 个参数的通用模板,并专门针对所有 10 个版本)。

于 2012-12-13T17:47:23.437 回答