C ++ confused between casting operator and Variadic constructor

C ++ (more specifically, the MinGW g ++ implementation) gets confused. I have a math vector class that contains an arbitrary number of an arbitrary element type. The type of element and the number of elements are indicated at compile time.

The Vector class is confused between one of its constructors and what I dubbed the "resize" operator. The resize operator allows the programmer to overlay a vector of one size into a vector of another arbitrary size. If the translation vector has more elements than the base vector, it fills 1. Here's the implementation:

/* * resize operator: * T is the type of element the base vector holds * N is the number of elements the base vector holds * rN is the size of the new vector */ template<typename T, unsigned int N, unsigned int rN> operator Vector<T, rN>() const { Vector<T, rN> resize; for (unsigned int i = 0; i < rN; i++) { resize[i] = i < N ? this->elements[i] : 1; } return resize; } 

The vector class also has a type-safe constructor of variations, which can take any number of any combination of elements (which must be of type T) and any number of vectors (which can contain any number of elements and must be of type T) as long as the number of elements added to the number of elements in the delivered vectors is equal to the number of elements that the construction vector contains.

Thus, this will be true:

 vec3 foo(vec2(1, 2), 3); 

but not that.

 vec3 bar(vec4(1, 2, 3, 4), 5); 

I guarantee that the correct number of elements was provided at compile time, recursing everything through them with a counter, then I use a static statement to make sure that the counter ends with the number of elements that the vector can contain. This works fine except for the following code:

 vec4 bar(1, 2, 3, 4); (vec3) bar; //PROBLEM HERE 

What happens is that C ++ believes that the (vec3) bar requests a variable constructor when in fact it should call the resize operator. I tried to make them explicit, but that didn't work. How can I guarantee that C ++ uses the resize operator when I have the code above and not the Variadic constructor?

In short, how can I tell C ++ to use this:

 //resize operator template<typename T, unsigned int N, unsigned int rN> Vector<T, N>::operator Vector<T, rN>(); 

instead of this:

 //constructor template<typename T, unsigned int N, typename ... Args> Vector<T, N>::Vector(Args ... arguments); 

when i have this code:

 (vec3) someVec4; 

If this is unclear, vec3 and vec4 are defined as such:

 typedef Vector<float, 3> vec3; typedef Vector<float, 4> vec4; 

EDIT :

News, everything! Even when I use static_cast (someVec4), it still calls the vec3 constructor with the vec4 argument. I do not know why.

OTHER CHANGE :

Creating an explicit constructor allows implicit responses to work, but not explicit ones. This means that this code works:

 vec3 foo = someVec4; 

But this code still gives me a static rejection statement:

 vec3 foo = static_cast<vec3>(someVec4); 

Which basically makes no sense, because I declared the varadic constructor explicit, and therefore should not be called there.

Also on request here SSCCE

TL version; DR is that my code invokes an explicit constructor when I try to explicitly call the typecasting operator, but not when I try to implicitly call it.

+7
source share
4 answers

I think the easiest thing was to get your code to work in order to replace the conversion statement with the conversion constructor. Since this constructor is more specialized than the variation constructor, it should always take precedence.

+1
source

No confusion. The constructor will always be preferable to the conversion function, and in your case, your type will always be constructive from any argument. Here is an example:

 struct foo { template<typename T> foo(T t); } template<typename T> foo::foo(T) { static_assert( std::is_same<T, int>::value, "" ); } 

Pay attention to the declaration of the template constructor (I deliberately separated the declaration from the definition): accepting T accepts any type of initializer. std::is_constructible<foo, T>::value is executed for all T , although only int will give the correct program. Other types will trigger static_assert when instantiating the constructor.

There is a secret sauce to achieve what you want, and its name is SFINAE - I hope you heard about this before. To explain, freely (in case you didn’t), if you move a potential error from the template body somewhere to the declaration, then the specializations that lead to such an error will be discarded in the process of overload resolution. To put it in code:

 struct foo { template< typename T , typename std::enable_if< std::is_same<T, int>::value , int >::type... > foo(T t); }; 

which will be the SFINAE version of the previous contrived example. With such an announcement then something like foo f = 42.; will not give the same error as before. The compiler will complain, for example. that there is no corresponding conversion from double to foo , as if the constructor does not exist at all. This is what we want, because if such a constructor does not exist, then the rules determine whether to search for the corresponding conversion operator. (Well, not that this is a big help in the double case, but no matter what.)

Please note that there are several ways to use SFINAE, and this is just my favorite form. You can find others by learning about SFINAE. (And for the record, it’s not so scary with the correct use of template aliases, where it looks like EnableIf<std::is_same<T, int>>... )

+4
source

Create your constructor and use:

 vec4 someVec4; // .... vec3 someVec3 = someVec4; 
+3
source

Looking at SSCCE , you can follow some cleaning steps.

The big problem with the universal constructor template is that it matches everything if the constructor without the template is not accurate. If you are mistaken even with the help of cv-qualification, a template of a universal constructor will be selected. When I had a similar problem, I was asked to add a marking value as the first parameter:

 enum my_marker { mark }; //... template<typename T, unsigned int N> class Vector { //... template<typename ... Args> explicit Vector(my_marker, Args ... args); }; //... Vector<int, 4> va( mark, a1, a2 ); 

Your other constructors will not use this marker, so now you can distinguish between them. By the way, you have another match with constructors that can take the value T :

 template<typename T, unsigned int N> class Vector { //... Vector( T empty ); Vector( std::initializer_list<T> set ); //... }; //... Vector<int, 4> vb{ 5 }; // always chooses the list ctr Vector<int, 4> vc( 6 ); // I think this uses the single-entry ctr. 

When you have an array as an argument to a function, it will be considered the default pointer, ignoring any size information. You must pass it by reference if you need to keep the size:

 template<typename T, unsigned int N> class Vector { //... Vector( T const (&set)[N] ); // "T set[N]" -> "T *set" //... }; //... int aa[ 4 ] = { 1, 2, 3, 4 }, bb[ 3 ] = { 5, 6, 7 }; Vector<int, 4> vd( aa ); // The new signature won't accept bb. 

This conversion between strings and pointers does not allow you to assign arrays directly, but they are implicitly assigned when calculating special functions. This means that your destination statement is not needed; the default code will do the right thing.

Have you heard of iterators? If so, then using these constructors plus delgating, standard algorithms and initializers can reduce your code.

 #include <algorithm> #include <cassert> #include <initializer_list> enum mark_t { mark }; template< typename T, unsigned N > class Vector { // The "set" functions are unnecessary, see below. public: // The automatically defined copy-ctr, move-ctr, copy-assign, and // move-assign are OK. T elements[N]; Vector() : elements{} {} // Vector() : Vector( T{} ) {} // ALTERNATE // Can be removed if following constructor uses a default argument. Vector(T empty) // Vector(T empty = T{}) // ALTERNATE { std::fill( elements, elements + N, empty ); } Vector(T const (&set)[N]) { std::copy( set, set + N, elements ); } Vector(std::initializer_list<T> set) : elements{} { assert( set.size() <= N ); std::copy( set.begin(), set.end(), elements ); // If you were willing to use std::for_each, why not use a more // appropriate algorithm directly? The lambda was overkill. // WARNING: there an inconsistency here compared to the cross- // version constructor. That one fills unused spots with ones, // while this one does it with zeros. // WARNING: there an inconsistency here compared to the single- // value constructor. That one fills all elements with the same // value, while this one uses that value for the first element but // fills the remaining elements with zeros. } template<typename ... Args> explicit Vector( mark_t, Args ... args) : elements{ args... } //: elements{ static_cast<T>(args)... } // ALTERNATE {} // Array members can now be directly initialized in the member part // of a constructor. They can be defaulted or have each element // specified. The latter makes the private "set" methods unnecessary. // The compiler will automatically issue errors if there are too // many elements for the array, or if at least one "Args" can't be // implicitly converted to "T", or if you have less than "N" elements // but "T" doesn't support default-initialization. On my system, the // example "main" flags int-to-float conversions as narrowing and post // warnings; the alternate code using "static_cast" avoids this. template < unsigned R > explicit Vector( Vector<T, R> const &v ) : Vector( static_cast<T>(1) ) { std::copy( v.elements, v.elements + std::min(R, N), elements ); } T &operator [](unsigned int param) { return this->elements[param]; } const T &operator [](unsigned int param) const { return this->element[param]; } }; typedef Vector<float, 2> vec2; typedef Vector<float, 3> vec3; typedef Vector<float, 4> vec4; int main() { vec4 someVec4(mark, 1, 2, 3, 4); vec3 foo = static_cast<vec3>(someVec4); return 0; } 
+2
source

All Articles