首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++隐式构造函数转换后的类型向上转换

C++隐式构造函数转换后的类型向上转换
EN

Stack Overflow用户
提问于 2018-08-25 04:57:10
回答 2查看 532关注 0票数 2

我有一个程序,它有一个基类Value,带有从Value继承的多个子类(例如IntValue)。每个类都有接受一个或多个参数的构造函数。下面的示例代码展示了我想要做的事情:

代码语言:javascript
复制
#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编译时,我会得到以下错误:

代码语言:javascript
复制
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.

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

有可能强迫编译器这样做吗?

EN

回答 2

Stack Overflow用户

发布于 2018-08-25 05:22:33

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

我想说的是,如果您有一个VariableValue对象,您可以很容易地在继承链上得到一个Value对象(或者引用它)。但是,如果不对Value对象进行明确说明,就不能从它的继承链向下走。

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

代码语言:javascript
复制
emit_add(VariableValue(x), 13, std::cout);
票数 2
EN

Stack Overflow用户

发布于 2018-08-25 07:25:01

以以下为反例:

代码语言:javascript
复制
class Value
{
public:
    Value() { }
    Value(int) { }
    Value(std::string const&) { }
};

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

当编译器看到emit_add接受两个ValueValue,并有适当的非显式构造函数接受std::stringint时,这种方法就会起作用。

C++没有提供的是根据给定参数从基类推断派生类,就像Some programmer dude denoted已经提供的那样。

不过,您可以为您提供一个包装器来完成此工作:

代码语言:javascript
复制
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<<),您现在可以这样做:

代码语言:javascript
复制
void emit_add(Wrapper const& lhs, Wrapper const& rhs, std::ostream& os)
{
    os << lhs << " + " << rhs << std::endl;
}

也会更舒服一些。

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

代码语言:javascript
复制
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
    );
}

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

代码语言:javascript
复制
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_;
};

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

如果仍然需要原始类型名称,可以将它们化名为:

代码语言:javascript
复制
using IntValue = TheValue<int>;

最后,如果它仅用于打印,则可以直接执行,完全绕过Value类:

代码语言:javascript
复制
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<<重载,如下面的点示例:

代码语言:javascript
复制
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 << ')';
    }
};

附带注意:上面的朋友操作符对于您自己的数据类型很方便(它仍然定义了一个独立的函数),如果您这样做的话:

代码语言:javascript
复制
template <typename T>
ostream& operator<<(ostream& s, Point<T> const& p)
{
    s << '(' p.x() << ", " << p.y() << ')';
}

在类之外(您仍然可以在类内声明它为朋友;如果不能或不能,当然不能使用私有成员,因此您依赖于上面演示的公共接口,假设xy是公共getter)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52014168

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档