【问题标题】:constructor with std::pair as argument: T a({1,2}) works, T a = {1,2} doesn't以 std::pair 作为参数的构造函数: T a({1,2}) 有效,T a = {1,2} 无效
【发布时间】:2018-05-11 10:55:40
【问题描述】:

我正在研究的组合算法需要大整数,作为练习,我想我会编写一个简单的 128 位整数类,但我遇到了与构造函数的一些不一致之处。

我有几个构造函数,因此您可以从另一个构造函数(使用隐式复制构造函数)或从 64 位整数或一对 64 位整数创建 uint128_t。这一切都有效,但让我感到困惑的是我可以使用这种语法:

uint128_t a = 123ull;
uint128_t b = a;

但不是:

uint128_t e = {123ull, 456ull};               // COMPILER ERROR
uint128_t f = std::make_pair(123ull, 456ull); // COMPILER ERROR

即使这些工作:

uint128_t c({123ull, 456ull});
uint128_t d(std::make_pair(123ull, 456ull));

我得到的错误是:

could not convert '{123, 345}' from '<brace-enclosed initializer list>' to 'uint128_t'
conversion from 'std::pair<long long unsigned int, long long unsigned int>' to non-scalar type 'uint128_t' requested

我可以只使用有效的语法,但我想知道我是否缺少一些简单的东西可以让uint128_t a = {1,2} 语法工作,因为这样可以更容易地将现有代码转换为使用 128位整数。

这里概述了哪些有效,哪些无效,以及课程的相关部分:

#include "uint128_t.hpp"

int main() {
    uint128_t a = 123ull;               // explixit constructor from uint64_t = ok
    uint128_t b = a;                    // implicit copy constructor = ok
    a = b;                              // assignment from uint128_t = ok
    b = 123ull;                         // assignment from uint64_t = ok
    a = {123ull, 456ull};               // assignment from pair of uint64_t = ok
    b = std::make_pair(123ull, 456ull); // assignment from pair of uint64_t = ok
    uint128_t c({123ull, 456ull});      // explixit constructor from pair = ok
    uint128_t d(std::make_pair(123ull, 456ull));

    uint128_t e = {123ull, 456ull};               // COMPILER ERROR
    uint128_t f = std::make_pair(123ull, 456ull); // COMPILER ERROR

    return 0;
}
#include <cstdint>

class uint128_t {
    private:

    uint64_t hi;
    uint64_t lo;

    public:

    uint128_t() {}
   ~uint128_t() {}
    uint128_t(uint64_t const& val) {
        hi = UINT64_C(0);
        lo = val;
    }
    uint128_t(std::pair<uint64_t const, uint64_t const> const& val) {
        hi = val.first;
        lo = val.second;
    }
    uint128_t const& operator=(uint128_t const&);
    uint128_t const& operator=(uint64_t const);
    uint128_t const& operator=(std::pair<uint64_t const, uint64_t const> const&);
}        
#include "uint128_t.hpp"

uint128_t const& uint128_t::operator=(uint128_t  const& other) {
    this->hi = other.hi;
    this->lo = other.lo;
    return *this;
}

uint128_t const& uint128_t::operator=(uint64_t const val) {
    this->hi = UINT64_C(0);
    this->lo = val;
    return *this;
}

uint128_t const& uint128_t::operator=(std::pair<uint64_t const, uint64_t const> const& val) {
    this->hi = val.first;
    this->lo = val.second;
    return *this;
}

【问题讨论】:

  • uint128_t::uint128_t(uint64_t, uint64_t)
  • 你为什么一直把ull字面量称为uint64_t?第二个错误实际上是由它引起的。你的文字是 not uint64_t。并且编译器无法将 std::pair&lt;unsigned long long, unsigned long long&gt; 转换为您的 std::pair&lt;uint64_t const, uint64_t const&gt;,因为这将是 second 在仅允许 one 此类转换的上下文中的隐式用户定义转换。
  • @AnT 删除 ull 或将其替换为 uint64_t 似乎没有什么区别。
  • @m69:那是因为您在模板参数中也有那个奇怪的const,它仍然会强制进行第二次转换。如果您只是在代码中添加强制转换,您的std::make_pair 将生成std::pair&lt;uint64_t, uint64_t&gt;,但您的构造函数需要std::pair&lt;const uint64_t, const uint64_t&gt;。这是两种不同的类型,它们会触发另一个用户定义的转换。也摆脱那个constcoliru.stacked-crooked.com/a/cb92e1103369b38c
  • @m69:它适用于a({123,456}),但不适用于a = {123,456}a{123,456}。后两者是list-initializations,而a({123,456})是直接初始化。

标签: c++ c++11 constructor assignment-operator std-pair


【解决方案1】:

在你的第一次复制初始化中

uint128_t e = {123ull, 456ull}; 

您正在使用多值列表来初始化非聚合类。根据 list-initialization 的规则,在这种情况下,编译器会考虑 std::initializer_list 构造函数,然后会考虑类中的双参数构造函数。您的类中不存在匹配的构造函数,因此初始化失败。

您可能希望编译器将{123ull, 456ull} 转换为std::pair,然后使用std::pair 来初始化e。但是 C++ 中的列表初始化不考虑这个初始化路径。 (注意,顺便说一句,这也看起来像是两个用户定义的转换的序列,见下文。)

你的第二次拷贝初始化

uint128_t f = std::make_pair(123ull, 456ull);

失败的原因与以下简化代码失败的原因相同

struct A { A(int) {} };
struct B { B(const A &) {} };

int main() {
  B b1(42);  // OK
  B b2 = 42; // Error
}

上述复制初始化在其转换顺序中需要两次 隐式用户定义转换:从intA,然后从AB。即使存在这样的转换,也不允许隐式应用其中的两个。复制初始化中最多允许一个隐式的用户定义转换。

在您的情况下,您请求从std::pair&lt;unsigned long long, unsigned long long&gt; 转换为std::pair&lt;uint64_t const, uint64_t const&gt;(构造函数参数类型),然后从std::pair&lt;uint64_t const, uint64_t const&gt; 转换为uint128_t - 两次转换。这些转化是存在的,但是连续两次太多了。

即使您确保std::make_pair 的参数具有uint64_t 类型,std::pair&lt;uint64_t const, uint64_t const&gt; 中的那些const 限定符仍然会强制进行额外的转换。您的make_pair 调用将产生一个std::pair&lt;uint64_t, uint64_t&gt; 值,该值必须转换为std::pair&lt;uint64_t const, uint64_t const&gt;,然后再转换为uint128_t

【讨论】:

    【解决方案2】:

    需要提供对应的构造函数


    uint128_t::uint128_t(uint64_t, uint64_t) //uint128_t x = {123ull, 456ull};
    uint128_t::uint128_t(const std::pair<uint64_t,uint64_t>& val) //uint128_t x = std::make_pair(123ull, 456ull);*
    

    注意ull 并不总是等于uint64_t

    【讨论】:

    • 谢谢。我将 std::pair 用于赋值运算符,并将该逻辑不必要地传递给构造函数。
    • std::pairs,恕我直言,不过,完全没有必要。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-04
    相关资源
    最近更新 更多