Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ confused between cast operator and variadic constructor

C++ (more specifically, MinGW's implementation of g++) is getting confused. I have a mathematical Vector class that contains an arbitrary number of an arbitrary type of element. The element type and number of elements are specified at compile time.

The Vector class is getting confused between one of it's constructors and what I have dubbed the "resize" operator. The resize operator allows the programmer to cast a vector of one size into a vector of another arbitrary size. If the cast vector has more elements than the base vector, it pads with 1's. 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 variadic constructor that can take any number of any combinations 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) so long as the number of bare elements added to the number of elements in the supplied vectors is equal to the number of elements that the constructing vector contains.

Thus this would be valid:

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

but not this.

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

I ensure that the proper number of elements has been supplied at compile time by recursing through them all with a counter, then I use a static assertion to make sure the counter ends up at number of elements the vector can contain. This normally works out well, except for the following code:

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

What's happening is that the C++ thinks that (vec3) bar is asking for the variadic constructor when, in fact, it should be calling the resize operator. I've tried making them explicit, but it hasn't worked. How do I ensure that C++ uses the resize operator when I have the above code and not the variadic constructor?

In short, how do 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;

In case it wasn't clear, vec3 and vec4 are defined as such:

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

EDIT:

News, everyone! Even when I use static_cast(someVec4) it still calls the vec3 constructor with a vec4 argument. I don't know why.

ANOTHER EDIT:

Making the constructor explicit allows implicit casts to work, but not explicit ones. Which is to say that this code works:

vec3 foo = someVec4;

But this code still gives me a static assertion failure:

vec3 foo = static_cast<vec3>(someVec4);

Which makes basically no sense because I declared the variadic constructor explicit, and thus it shouldn't be called there.

Also, upon request, here's an SSCCE

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

like image 650
Publius Avatar asked Aug 11 '12 11:08

Publius


People also ask

What are the different types of casting operators in C++?

C++ supports following 4 types of casting operators: const_cast is used to cast away the constness of variables. Following are some interesting facts about const_cast. 1) const_cast can be used to change non-const class members inside a const member function.

What is the use of constructors in C++?

Constructor operators are used in constructor expressions to create a result that can be used at operand positions. The syntax for constructor expressions is … operator type ( …

What is variadic function in C programming?

Variadic functions are functions that can take a variable number of arguments. In C programming, a variadic function adds flexibility to the program. It takes one fixed argument and then any number of arguments can be passed. The variadic function consists of at least one fixed variable and then an ellipsis (…) as the last parameter.

What is the use of Conv operator in C++?

The conversion operator CONV is a constructor operator that converts a value into the type specified in type.


Video Answer


1 Answers

There is no confusion. A constructor will always be preferred to a conversion function, and in your case your type is always constructible from any kind of argument. Here's a much reduced example:

struct foo {
    template<typename T>
    foo(T t);
}

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

Notice the declaration of the template constructor (I separated declaration from definition on purpose): by taking T, any kind of initializer is accepted. std::is_constructible<foo, T>::value holds for all T, even though only int will yield a correct program. Other types will trigger the static_assert when the constructor is instantiated.

There is a secret sauce to achieve what you want, and its name is SFINAE -- hopefully you've heard of it before. To explain loosely (in case you haven't), if you move a potential error from the body of the template to somewhere in the declaration then specializations that would yield 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 would be the SFINAE version of the previous contrived example. With such a declaration then something like foo f = 42.; will not yield the same kind of error as before. The compiler would complain e.g. that there is no appropriate conversion from double to foo, as if the constructor didn't exist at all. Which is what we want because if no such constructor exists, then the rules dictate that an appropriate conversion operator will be searched for. (Well, not that it's a big help in the case of double but nevermind that.)

Note that there are several ways to make use of SFINAE and this one only happens to be my favourite form. You can find others by learning about SFINAE. (And for the record it isn't as scary with the proper use of template aliases, where it ends up looking like EnableIf<std::is_same<T, int>>....)

like image 199
Luc Danton Avatar answered Sep 19 '22 12:09

Luc Danton