C++转换运算符到chrono :: duration - 适用于c ++ 17但不适用于C++ 14或更低版本

Sla*_*vov 8 c++ conversion-operator c++-chrono c++14 c++17

下面的代码使用gcc 7.1.0编译,C++ 17设置,但不使用C++ 14 set(或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;
}
Run Code Online (Sandbox Code Playgroud)

Sto*_*ica 9

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

duration( const duration& ) = default;

template< class Rep2 >
constexpr explicit duration( const Rep2& r );
Run Code Online (Sandbox Code Playgroud)

模板化的类型更适合类型的参数Convert.但是如果Rep2(aka Convert)可以隐式转换为表示类型,它只会参与重载决策std::chrono::duration.对于milliseconds,就是long在Wandbox上.您的int64_t转换运算符使隐式转换成为可能.

但这是抓住了.检查此隐式转换不会考虑转换成员函数的cv限定符.因此选择了重载,但它接受const引用.而您的用户定义转换运算符const不合格!@Galik在你的帖子的评论中注意到了这一点.因此,转换在内部失败milliseconds.

那怎么解决呢?两种方式:

  1. 标记转换运算符const.然而,这将选择转换int64_tmi.

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

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


Bar*_*rry 7

这是一个非常有趣的案例.它无法在C++ 14上编译的原因是由StoryTeller正确解释的,并且可以说是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);  // ? 
Run Code Online (Sandbox Code Playgroud)

根据今天的语言规则,复制初始化b是可以的,我们使用转换函数.但直接初始化b并不合适,我们必须使用A已删除的复制构造函数.

另一个激励的例子是:

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

Dog d;
Cat c(d);
Run Code Online (Sandbox Code Playgroud)

在这里,我们必须再次进行Cat(Cat&& )- 在这种情况下,它是格式良好的,但由于临时实现而禁止复制省略.

所以,这个问题的建议的解决方法是要考虑这两个构造函数直接初始化转换功能.在这里和OP的例子中,这都会产生"预期"行为 - 直接初始化只会使用转换函数作为更好的匹配.gcc和clang都采用C++ 17模式,这就是为什么这些例子今天编译的原因.