繁体   English   中英

ctor 在 clang 和 gcc 上的单个和多个 std::initializer_list ctor 之间不明确,但不是 msvc

[英]ctor is ambiguous between single and multiple std::initializer_list ctors on clang and gcc but not msvc

我有一个用于创建 2D 矩阵的嵌套 initializer_list ctor。 效果很好。 但后来我决定使用单个初始值设定项列表添加一个简化的行向量矩阵(n 行,1 列)。 这样我就可以创建这样的行矩阵: Matrix2D<int> x{1,2,3}而不必这样做: Matrix2D<int> x{{1},{2},{3}} 当然,这需要一个单独的 ctor。

一切正常,包括在 MSVC 的 constexpr 中。 但是当使用 gcc 和 clang 检查时,我得到了模棱两可的 ctors。 当列表嵌套并且 MSVC 完全按预期运行时,我似乎很清楚。

这是代码:

// Comment out next line for only nested initializer_list ctor
#define INCLUDE_EXTRA_CTOR
#include <memory>
#include <numeric>
#include <initializer_list>
#include <exception>
#include <stdexcept>
#include <concepts>

using std::size_t;
template <typename T>
class Matrix2D {
    T* const pv;    // pointer to matrix contents
public:
    const size_t cols;
    const size_t rows;

    // default ctor;
    Matrix2D() noexcept : pv(nullptr), cols(0), rows(0) {}

    // 2D List initialized ctor
    Matrix2D(std::initializer_list<std::initializer_list<T>> list) :
        pv((list.begin())->size() != 0 ? new T[list.size() * (list.begin())->size()] : nullptr),
        cols(pv != nullptr ? (list.begin())->size() : 0),
        rows(pv != nullptr ? list.size() : 0)
    {
        if (pv == nullptr)
            return;
        for (size_t row = 0; row < list.size(); row++)
        {
            if (cols != (list.begin() + row)->size())
                throw std::runtime_error("number of columns in each row must be the same");
            for (size_t col = 0; col < cols; col++)
                pv[cols * row + col] = *((list.begin() + row)->begin() + col);
        }
    }
#ifdef INCLUDE_EXTRA_CTOR
    // Row initialized ctor, rows=n, cols=1;
    Matrix2D(std::initializer_list<T> list) :
        pv(list.size() != 0 ? new T[list.size()] : nullptr),
        cols(pv != nullptr ? 1 : 0),
        rows(pv != nullptr ? list.size() : 0)
    {
        if (pv == nullptr)
            return;
        for (size_t row = 0; row < rows; row++)
        {
            pv[row] = *(list.begin() + row);
        }
    }
#endif

    // dtor
    ~Matrix2D() { delete[] pv; }
};

int main()
{
    // Tests of various possible null declarations 
    Matrix2D<int> x1{ };        // default
    Matrix2D<int> x2{ {} };     // E0309, nested init list with 1 row, 0 cols, forced to 0 rows, 0 cols
    Matrix2D<int> x3{ {},{} };  // E0309, nested init list with 2 rows, 0 cols, forced to 0 rows, 0 cols

    // typical declaration
    Matrix2D<int> x4{ {1,2},{3,4},{5,6} };  // nested init list with 3 rows, 2 cols
    // standard row vector declaration
    Matrix2D<int> x5{ {1},{2},{3} };  // E0309, init list with 3 rows, 1 col

#ifdef INCLUDE_EXTRA_CTOR
    // row vector declaration
    Matrix2D<int> x6{ 1,2,3 };  // init list with 3 rows, 1 col
#endif
}

E0309 是 MSVC 智能感知模棱两可的 ctor 错误。 但是,编译没有错误为什么 gcc 和 clang 扣除不明确? 有解决方法吗?

编译器资源管理器

但是,编译没有错误为什么 gcc 和 clang 扣除不明确?

此处出现歧义是因为{}{1}也可以初始化单个int

有解决方法吗?

模板化您的专用构造函数,使{}永远不会被推导出为仍然适用于{1,2,3}initializer_list

#ifdef INCLUDE_EXTRA_CTOR
    // Row initialized ctor, rows=n, cols=1;
    template<class U>
    Matrix2D(std::initializer_list<U> list) :
        pv(list.size() != 0 ? new T[list.size()] : nullptr),
        cols(pv != nullptr ? 1 : 0),
        rows(pv != nullptr ? list.size() : 0)
    {
        fmt::print("1: {}\n", list);
        if (pv == nullptr)
            return;
        for (size_t row = 0; row < rows; row++)
        {
            pv[row] = *(list.begin() + row);
        }
    }
#endif

如果你想要Matrix2D<double> d1{ 1,2,4. } Matrix2D<double> d1{ 1,2,4. }工作,那么您可以使用type_identity_t在模板参数推导中建立非推导上下文:

// Row initialized ctor, rows=n, cols=1;
template<class U = T>
Matrix2D(std::initializer_list<std::type_identity_t<U>> list) :
    pv(list.size() != 0 ? new T[list.size()] : nullptr),
    cols(pv != nullptr ? 1 : 0),
    rows(pv != nullptr ? list.size() : 0)
{

演示

如果我们检查编译器错误:

<source>:62:29: error: call of overloaded 'Matrix2D(<brace-enclosed initializer list>)' is ambiguous
   62 |     Matrix2D<int> x3{ {},{} };  // nested init list with 2 rows, 0 cols, forced to 0 rows, 0 cols
      |                             ^
<source>:39:5: note: candidate: 'Matrix2D<T>::Matrix2D(std::initializer_list<_Tp>) [with T = int]'
   39 |     Matrix2D(std::initializer_list<T> list) :
      |     ^~~~~~~~
<source>:22:5: note: candidate: 'Matrix2D<T>::Matrix2D(std::initializer_list<std::initializer_list<_Tp> >) [with T = int]'
   22 |     Matrix2D(std::initializer_list<std::initializer_list<T>> list) :
      |     ^~~~~~~~

然后它有点用<brace-enclosed initializer list>告诉我们它既不识别initializer_list<T>也不识别initializer_list<initializer_list<T>> 不幸的是, initializer_list是一个“运行时”构造:它的大小在编译时是未知的。

initializer_list有一个额外的坏属性:它比复制或移动构造器匹配得更好,因此,如果您在打算复制或移动初始化 object 时不小心使用了列表初始化,该错误将非常令人困惑:)

相反,如果您在编译时知道矩阵布局,则可以使用一些带有std::index_sequence的模板并将 arrays 传递给构造函数。 如果不这样做,您仍然可以使用std::initializer_list并将大小指定为附加 arguments。

这是我前段时间为自己写的试图解决类似问题( godbolt )的摘录:

#include <array>
#include <cstddef>
#include <cstring>
#include <type_traits>
#include <utility>

template <std::size_t, typename T>
using enumerate = T;

template <typename Precision, typename NthInnerArrayIndexSequence,
          std::size_t InnerDimension>
class MatrixImpl;

template <typename Precision, std::size_t... NthInnerArrayPack,
          std::size_t InnerDimension>
class MatrixImpl<Precision, std::index_sequence<NthInnerArrayPack...>,
                 InnerDimension> {
   public:
    MatrixImpl() = default;

    // Initialization is row-major. Every inner array is a row.
    constexpr explicit MatrixImpl(
        enumerate<
            NthInnerArrayPack,
            Precision const (&)[InnerDimension]>... nth_inner_array) noexcept {
        // memcpy() is more efficient, but it is not constexpr.
        if (std::is_constant_evaluated()) {
            ((insert(mat_, nth_inner_array, NthInnerArrayPack)), ...);
        } else {
            ((std::memcpy(mat_.data() + NthInnerArrayPack * InnerDimension,
                          nth_inner_array, sizeof nth_inner_array)),
             ...);
        }
    }

   protected:
    std::array<Precision, sizeof...(NthInnerArrayPack) * InnerDimension> mat_;

   private:
    constexpr void insert(decltype(mat_)& dst,
                          Precision const (&src)[InnerDimension],
                          std::size_t nth_pack) noexcept {
        for (size_t i{nth_pack * InnerDimension}, j{0UL};
             i < nth_pack * InnerDimension + InnerDimension; ++i, ++j)
            dst[i] = src[j];
    }
};

// Base class with routines for any NxM Matrix.
template <typename Precision, std::size_t OuterDimension,
          std::size_t InnerDimension>
class MatrixBase
    : public MatrixImpl<Precision, std::make_index_sequence<OuterDimension>,
                        InnerDimension> {
    static_assert(std::is_same_v<Precision, float> ||
                      std::is_same_v<Precision, double>,
                  "Matrix only supports single and double precision.");
    static_assert(OuterDimension * InnerDimension != 0,
                  "Both dimensions must be non-zero.");

    using base = MatrixImpl<Precision, std::make_index_sequence<OuterDimension>,
                            InnerDimension>;

   protected:
    using impl = base;

   public:
    MatrixBase() = default;
    using MatrixImpl<Precision, std::make_index_sequence<OuterDimension>,
                     InnerDimension>::MatrixImpl;

    // some useful routines for all kinds of matrices
};

// You can implement a default NxM matrix if you want.
template <typename Precision, std::size_t OuterDimension,
          std::size_t InnerDimension>
class Matrix;

// Square matrix. Reuse base class routines, and add new ones.
template <typename Precision, std::size_t Dimension>
class Matrix<Precision, Dimension, Dimension>
    : public MatrixBase<Precision, Dimension, Dimension> {
    using base = MatrixBase<Precision, Dimension, Dimension>;

   public:
    using typename base::impl;

    Matrix() = default;
    using MatrixBase<Precision, Dimension, Dimension>::MatrixBase;

    // some useful square matrix routines
};

// Can be a Nx1 Matrix specialisation here: a Vector

// 2x2 Matrix
template <typename Precision, std::size_t InnerDimension>
Matrix(enumerate<0, Precision const (&)[InnerDimension]>,
       enumerate<1, Precision const (&)[InnerDimension]>)
    -> Matrix<Precision, InnerDimension, InnerDimension>;

using Mat2f = Matrix<float, 2, 2>;

int main() { constexpr Matrix mat1{{-3.f, 5.f}, {1.f, -2.f}}; }

事实上,现在您甚至可以使用constexpr矩阵。 这肯定是不可能的initializer_list :)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM