对C++基类的模糊函数调用

Ambiguous Function Calls to C++ base classes

本文关键字:模糊 函数调用 基类 C++      更新时间:2023-10-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中的错误。解决歧义的正确方法是什么?或者我应该考虑不同的设计?

为了解决歧义,可以进行

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

(参见示例)

但这并不像人们所希望的那样稳健。特别是,不能打印可转换为类型列表中某个类型的对象。

然而,通过一些模板元编程,可以将其发送到正确的打印机。要做到这一点,您必须从Ts...中选择一种类型,U可以转换为该类型,并调用右侧的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
{};

(参见示例)

我不喜欢回答自己的问题,但我已经根据这里的注释创建了以下解决方案。它将所有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
}

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

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

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