【问题标题】:Force compiler to choose copy constructor with const T& as a parameter强制编译器选择以 const T& 作为参数的复制构造函数
【发布时间】:2016-09-10 08:51:37
【问题描述】:

我正在编写一个类,其中有一个模板构造函数和复制构造函数。每次我想用非 const 对象调用复制构造函数时,都会选择模板化构造函数。如何强制编译器选择复制构造函数?

这里是 mcve:

#include <iostream>

struct foo
{
    foo()
    {
        std::cout << "def constructor is invoked\n";
    }

    foo(const foo& other)
    {
        std::cout << "copy constructor is invoked\n";
    }

    template <typename T>
    foo(T&& value)
    {
        std::cout << "templated constructor is invoked\n";
    }
};

int main()
{
    foo first;
    foo second(first);
}

删除函数不是我想要的。

【问题讨论】:

  • 调用 ctor 时,不应该将参数转换为const &amp;foo 吗? ctor 用于 const args,因此请提供一个。
  • @PeterA.Schneider,我在写std::variant。我认为人们不会喜欢选角。我想保持用户端干净
  • 避免所有这些恶作剧的一种方法是为转发构造函数提供一个虚拟的第一个参数,这样就不会有混淆的可能性
  • 这可能有助于更清楚地思考你想要什么。你想禁止所有T 的模板构造函数,就像foo 一样吗? (foo&amp;&amp;foo&amp;const foo&amp;volatile foo&amp;,...)。这应该很容易用一点enable_if。或者只是在存在可行的非模板构造函数的特定情况下禁止模板构造函数? (我认为后者是不可能的)
  • @M.M, std::variant 支持没有虚拟第一个参数的模板化构造函数。我想写出尽可能严格符合标准的实现。

标签: c++ c++14 copy-constructor


【解决方案1】:

添加另一个构造函数:

foo(foo& other) : foo( const_cast<const foo&>(other))  // for non-const lvalues
{
}

您的示例代码中的first 对象是一个非常量左值,因此编译器更喜欢foo(foo&amp;) 而不是foo(const &amp;)。前者由模板提供(带有T=foo&amp;),因此被选中。

此解决方案涉及为foo(foo&amp;) 提供一个(非模板)构造函数,然后将构造函数委托给复制构造函数,方法是将其转换为对常量的引用

更新,我刚刚意识到模板也将采用foo 右值。这里有很多选项,但我想最简单的也是为foo(foo&amp;&amp;) 添加一个委托,类似于上面的那个

foo(foo&& other) : foo( const_cast<const foo&>(other))  // for rvalues
{
}

【讨论】:

  • 我专攻右值参考。还有其他方法吗?我相信static_cast 是可以的,因为使对象更 const 是合法的。我已经有很多演员表了,但想看看有没有其他方法。
  • static_cast 肯定没问题,但我担心如果我更改类型会破坏代码;例如通过从基类型转换为派生类型。这就是我使用const_cast 的原因;只是为了让我自己和编译器清楚,只有 const-ness 应该改变
【解决方案2】:

问题在于first 是可变的,因此对它的引用是foo&amp;,它比const foo&amp; 更容易绑定到通用引用T&amp;&amp;

大概,您的意思是 T 是任何非 foo 类?

在这种情况下,enable_if 的一点点诡计向编译器表达了意图,而不必编写大量虚假的重载。

#include <iostream>

struct foo
{
    foo()
    {
        std::cout << "def constructor is invoked\n";
    }

    foo(const foo& other)
    {
        std::cout << "copy constructor is invoked\n";
    }

    template <typename T, std::enable_if_t<not std::is_base_of<foo, std::decay_t<T>>::value>* = nullptr>
    foo(T&& value)
    {
        std::cout << "templated constructor is invoked\n";
    }

};

int main()
{
    foo first;
    foo second(first);
    foo(6);
}

预期输出:

def constructor is invoked
copy constructor is invoked
templated constructor is invoked

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-05
    • 1970-01-01
    • 1970-01-01
    • 2012-10-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多