How to resolve this ambiguous call to the template constructor?

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; // PROBLEMS: Matrix3x1({{1}, {2}, {3}}); // error: ambiguous Matrix1x1({{1}}); // error: ambiguous } 

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?

+5
source share
1 answer

Uncertainty in the one-dimensional case is between these two constructors:

 TMatrixMxN(std::initializer_list<T> values); TMatrixMxN(std::initializer_list<std::initializer_list<T>> values) 

Because {{1}, {2}, {3}} can also be interpreted as an overly aggressive version of {1, 2, 3} . Please note that this is not size N or M , this still fails:

 Matrix3x3 m9({{1}, {2}, {3}}); // same error 

The simplest solution is to disable the ambiguity by turning the less desirable constructor into a constructor template with dummy parameters:

 template <size_t _=M> TMatrixMxN(std::initializer_list<T> values) { std::cout << "Row initializer" << std::endl; } 

We have not changed anything - we just created this template. Now, if both constructors are the same, it is preferable to initializer_list<initializer_list<T>> , since this is not a template. If you want to actually disable this parameter for M==1 or N==1 , you can add enable_if_t there:

 template <size_t m=M, size_t n=N, class = std::enable_if_t<(m != 1 && n != 1)>> TMatrixMxN(std::initializer_list<T> values) { std::cout << "Row initializer" << std::endl; } 
+3
source

Source: https://habr.com/ru/post/1245564/


All Articles