I have a matrix class template:
#include <iostream> #include <array> #include <initializer_list> #include <utility> #include <type_traits> #include <cstddef> enum ColumnFill { COLUMNS }; template <typename T, std::size_t M, std::size_t N> struct TMatrixMxN { TMatrixMxN(T x = T(0)) { std::cout << "Default" << std::endl; } TMatrixMxN(std::initializer_list<T> values) { std::cout << "Row initializer" << std::endl; } TMatrixMxN(std::initializer_list<T> values, ColumnFill dummy) { std::cout << "Column initializer" << std::endl; } TMatrixMxN(std::initializer_list<std::initializer_list<T>> values) { std::cout << "Value initializer" << std::endl; } TMatrixMxN(const std::array<std::array<T, N>, M> &values) { std::cout << "From array" << std::endl; } TMatrixMxN(const TMatrixMxN<T, M - 1, N - 1> &x) { std::cout << "From lower dimension" << std::endl; } TMatrixMxN(const TMatrixMxN &x) { std::cout << "Copy" << std::endl; } TMatrixMxN(TMatrixMxN &&x) { std::cout << "Move" << std::endl; } }; typedef TMatrixMxN<float, 1, 1> Matrix1x1; typedef TMatrixMxN<float, 2, 2> Matrix2x2; typedef TMatrixMxN<float, 3, 3> Matrix3x3; typedef TMatrixMxN<float, 3, 1> Matrix3x1;
All dandy, until I use a matrix that has 1 in any of its sizes:
int main() { std::array<std::array<float, 3>, 3> arr{{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}}; Matrix3x3 m1; Matrix3x3 m2({1, 2, 3}); Matrix3x3 m3({1, 2, 3}, COLUMNS); Matrix3x3 m4({{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}); Matrix3x3 m5(arr); Matrix3x3 m6(Matrix2x2({{1, 2}, {3, 4}})); Matrix3x3 m7(m6); Matrix3x3 m8(std::move(m7)); std::cout << std::endl; TMatrixMxN<float, 3, 2>({{1, 2}, {3, 4}, {5, 6}}); std::cout << std::endl;
I do not know how to solve this problem well (I want these calls to call the value initializer constructor)
When compiling with g++ -std=c++11 Ambiguous.cpp
compiler considers that the following constructors are candidates for the 3x1 matrix: move, copy, from-lower-dimension, initialization initializer, string initializer. For a 1x1 matrix, it also contains a list of array and default values.
Things I tried:
- Using SFINAE to make an overload condition, that T must be an arithmetic type (
std::is_arithmetic
), because I thought it might sometimes seem like it was initializer_list
, but didn't change anything. I realized that this is a useless test, because in fact she already knows well and well that T in this example is floating. - Adding the
explicit
keyword to these constructors: initialization initializer, row initializer, column initializer, from-array and from-lower-dimension, because I thought some implicit calls continue, but that also didn't change anything - Creating a typedef "fil" (like a "list of initializers float") and converting the code to
{fil{1}}
, since I realized that the finished material could not be interpreted as a list of initializers - then it worked as intended. However, I do not think this fix is โโgood enough.
Can this be done at all?