C++隐式构造函数转换,后跟类型向上转换



我有一个程序,它有一个基类Value有多个子类(例如IntValue(继承自Value.其中每个类都有接受一个或多个参数的构造函数。下面是示例代码,显示了我希望能够执行的操作:

#include <iostream>
class Value {
public:
Value() {}
virtual void print(std::ostream& os) const {}
};
class IntValue: public Value {
public:
IntValue(int val): val_(val) {}
void print(std::ostream& os) const override { os << val_; }
private:
int val_;
};
class VariableValue: public Value {
public:
VariableValue(const std::string& name): name_(name) {}
void print(std::ostream& os) const override { os << name_; }
private:
const std::string name_;
};
void emit_add(const Value& lhs, const Value& rhs, std::ostream& os) {
lhs.print(os);
os << " + ";
rhs.print(os);
os << std::endl;
}
template <class ValueType>
void emit_add(const ValueType& lhs, const ValueType& rhs, std::ostream &os) {
lhs.print(os);
os << " + ";
rhs.print(os);
os << std::endl;
}
int main() {
// all these work                                                                              
emit_add<IntValue>(12, 13, std::cout); // implicit constructors                                
emit_add<VariableValue>(std::string("x"), std::string("y"), std::cout); // implicit constructo
rs                                                                                               
emit_add(VariableValue(std::string("x")), IntValue(1), std::cout); // implicit upcasting       
// this doesn't                                                                                
emit_add(std::string("x"), 13, std::cout); // implicit constor + implicit upcasting            
return -1;
}

当我尝试使用clang 9.1.0进行编译时,出现以下错误:

test.cpp:47:3: error: no matching function for call to 'emit_add'
emit_add(std::string("x"), 13, std::cout); // implicit constor + implicit upcasting
^~~~~~~~
test.cpp:25:6: note: candidate function not viable: no known conversion from 'std::string' (aka
'basic_string<char, char_traits<char>, allocator<char> >') to 'const Value' for 1st
argument
void emit_add(const Value& lhs, const Value& rhs, std::ostream& os) {
^
test.cpp:33:6: note: candidate template ignored: deduced conflicting types for parameter
'ValueType' ('std::__1::basic_string<char>' vs. 'int')
void emit_add(const ValueType& lhs, const ValueType& rhs, std::ostream &os) {
^
1 error generated.

我的理解是编译器无法为 THEVariableValue调用隐式构造函数,然后将其向上转换为类型Value,但它显然可以单独执行这两项操作。

是否可以强制编译器执行此操作?

VariableValueValue(因为继承是"关系(,但Value不是VariableValue(继承的"是"关系是单向的(。

我想说的是,如果你有一个VariableValue对象,你可以很容易地沿着继承链向上获取一个Value对象(或它的引用(。但是你不能走另一条路,从Value对象的继承链向下,而不明确说明它。

您需要显式构造一个VariableValue对象并将其传递给您的函数:

emit_add(VariableValue(x), 13, std::cout);

考虑以下作为例:

class Value
{
public:
Value() { }
Value(int) { }
Value(std::string const&) { }
};
emit_add(x, 13, std::cout);

现在这将起作用,因为编译器看到emit_add接受两个ValueValue让适当的非显式构造函数接受std::stringint

C++没有提供的是根据给定的参数从基类推断派生类,正如一些程序员已经表示的那样。

但是,您可以提供一个包装器来为您完成这项工作:

class Wrapper
{
std::unique_ptr<Value> value;
public:
Wrapper(int n) : value(new IntValue(n)) { }
Wrapper(std::string) : value(new VariableValue(n)) { }
friend std::ostream& operator<<(std::ostream& s, Wrapper const& w)
{
w.value->print(s);
return s;
}
};

如果这真的比直接指定类型(就像一些程序员所做的那样(更好,那是一个品味问题。另一方面,如上所述(使用operator<<(,您现在可以执行以下操作:

void emit_add(Wrapper const& lhs, Wrapper const& rhs, std::ostream& os)
{
os << lhs << " + " << rhs << std::endl;
}

这也更舒服一点...

另一种方法可能是重载模板:

void emit_add(Value const& x, Value const& y, std::ostream& os);
template <typename TX, typename TY>
void emit_add(TX&& x, TY&& y, std::ostream& os)
{
emit_add
(
static_cast<Value const&>(TheValue<TX>(std::forward<TX>(x))),
static_cast<Value const&>(TheValue<TY>(std::forward<TY>(y))),
os
);
}

上面的强制转换是必要的,否则,模板本身将是更好的匹配,并且不会选择非模板,从而导致无休止的递归。我将具体值转换为模板:

template <typename T>
class TheValue : public Value
{
public:
TheValue(T&& t)
: val_(std::forward<T>(t))
{  }
void print(std::ostream& os) const override
{
os << val_;
}
private:
T val_;
};

如果此默认模式与特定类型的具体需求不匹配,则可以将其专用于该类型以满足您的需求。

如果仍需要原始类型名称,可以为其设置别名:

using IntValue = TheValue<int>;

最后,如果只是为了打印出来,你可以直接做,完全绕过Value类:

template <typename TX, typename TY>
void emit_add(TX&& x, TY&& y, std::ostream& os)
{
std::cout << std::forward<TX>(x) << " + " << std::forward<TY>(y) << std::endl;
}

如果现在有一些要打印的自定义类型,只需提供operator<<重载,例如以下点示例:

template <typename T>
class Point
{
T m_x, m_y;
public:
// constructors, calculations, ... (whatever you might need/find useful)
friend ostream& operator<<(ostream& s, Point const& p)
{
s << '(' p.m_x << ", " << p.m_y << ')';
}
};

旁注:上面的 friend 运算符对于您自己的数据类型很方便(它仍然定义了一个独立的函数(,只要您这样做:

template <typename T>
ostream& operator<<(ostream& s, Point<T> const& p)
{
s << '(' p.x() << ", " << p.y() << ')';
}

在类外(你仍然可以在里面声明它为朋友;如果你不这样做或不能,你当然不能使用私有成员,所以你依赖于上面演示的公共接口,假设xy是公共getter(。

最新更新