【问题标题】:C++ conversion operator to chrono::duration - works with c++17 but not C++14 or lessC++ 转换运算符到 chrono::duration - 适用于 c++17 但不适用于 C++14 或更低版本
【发布时间】:2018-01-21 10:07:32
【问题描述】:

以下代码使用带有 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;
}

【问题讨论】:

  • 我不确定它的来龙去脉,但如果你让你的两个函数const 它为我编译。 (添加缺失部分后)
  • 如果我不将它们设为const 但我将它们设为explicit,它也会编译。再说一次,不知道它的来龙去脉。
  • 请发布编译器错误信息

标签: c++ c++14 chrono conversion-operator c++17


【解决方案1】:

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

duration( const duration& ) = default;

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

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

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

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

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

  2. 将转换为int64_t 标记为explicit。现在模板化的重载不会参与m 的重载解析。

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

【讨论】:

    【解决方案2】:

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

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

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

    struct A 
    { 
      A(); 
      A(const A&) = delete; 
    }; 
    struct B 
    { 
      operator A(); 
    }; 
    
    B b; 
    A a1 = b; // OK 
    A a2(b);  // ? 
    

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

    另一个激励的例子是:

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

    在这里,我们必须再次检查Cat(Cat&amp;&amp; ) - 在这种情况下,它的格式是正确的,但由于temporary materialization 而禁止复制省略。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-07
      • 1970-01-01
      • 1970-01-01
      • 2017-03-25
      • 2012-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多