8

以下代码使用带有 C++17 集的 gcc 7.1.0 编译,但不能使用 C++14 集(或 Visual Studio 2017)编译。在Wandbox上很容易复制。

必须做些什么才能使其与 C++11/14 一起使用?

#include <iostream>
#include <chrono>

int main()
{
    struct Convert
    {
        operator std::chrono::milliseconds()
        {
            std::cout << "operator std::chrono::milliseconds" << std::endl;
            return std::chrono::milliseconds(10);
        }

        operator int64_t ()
        {
            std::cout << "operator int64_t" << std::endl;
            return 5;
        }
    };

    Convert convert;

    std::chrono::milliseconds m(convert);
    std::cout << m.count() << std::endl;
    int64_t i(convert);
    std::cout << i << std::endl;
}
4

2 回答 2

9

让我们从为什么这在 C++14 中不起作用开始。有两个相关的 c'tors std::chrono::durationstd::chrono::milliseconds别名为):

duration( const duration& ) = default;

template< class Rep2 >
constexpr explicit duration( const Rep2& r );

模板化的参数更适合 type 的参数ConvertRep2但只有在(aka Convert) 可隐式转换为 的表示类型时,它才会参与重载决议std::chrono::duration。对于milliseconds,那是long在 Wandbox 上。您的int64_t转换运算符使隐式转换成为可能。

但这就是问题所在。对此隐式转换的检查不考虑转换成员函数的 cv 限定符。因此选择了重载,但它通过 const 引用接受。而且您的用户定义的转换运算符const不合格!@Galik在您帖子的评论中指出了这一点。因此,转换在 的 c'tor 内失败milliseconds

那么如何解决呢?两种方式:

  1. 标记转换运算符const。但是,这将为和选择转换int64_t为。mi

  2. 将转换标记int64_texplicit。现在模板化的重载不会参与m.

最后,为什么这在 C++17 中有效?这将保证复制省略。由于您Convert已转换为std::chrono::milliseconds,因此它用于m直接初始化。它的细节甚至不需要选择复制构造函数,只是稍后省略它。

于 2018-01-21T10:42:55.287 回答
7

这是一个非常有趣的案例。StoryTeller正确解释了它无法在 C++14 上编译的原因,并且可以说是 LWG 缺陷 - 转换构造函数的要求Rep2是可转换为rep,但该构造函数的主体尝试将 a 转换Rep2 constrep- 和OP中的特定示例,这是格式错误的。现在是LWG 3050

但在 C++17 中,标准中相关的明确规则都没有改变。直接初始化(例如std::chrono::milliseconds m(convert);)仍然只考虑构造函数,并且构造函数中重载决议的最佳匹配仍然是导致程序无法在 C++14 中编译的有缺陷的转换构造函数。

然而,gcc 和 clang 显然都决定实施一个突出的核心问题,尽管还没有任何措辞。考虑:

struct A 
{ 
  A(); 
  A(const A&) = delete; 
}; 
struct B 
{ 
  operator A(); 
}; 

B b; 
A a1 = b; // OK 
A a2(b);  // ? 

根据今天的语言规则,复制初始化 fromb是可以的,我们使用转换功能。但是直接初始化 fromb是不行的,我们必须使用A's deleted 复制构造函数。

另一个鼓舞人心的例子是:

struct Cat {};
struct Dog { operator Cat(); };

Dog d;
Cat c(d);

在这里,我们必须再一次经历Cat(Cat&& )——在这种情况下,它是格式良好的,但由于临时实现而抑制了复制省略。

所以这个问题的建议解决方案是考虑直接初始化构造函数转换函数。在此处的示例和 OP 的示例中,这将产生“预期的”行为 - 直接初始化只会使用转换函数作为更好的匹配。gcc 和 clang 都在 C++17 模式下采用这条路线,这就是今天编译示例的原因。

于 2018-01-22T15:21:54.333 回答