![](/img/trans.png)
[英]const auto std::initializer_list difference between Clang and GCC
[英]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.