Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ declares a function instead of calling a complex constructor

First of, I know there are similar questions already on stackoverflow (this, this and this one) and that is why I understand the why of my problem. Unfortunately, that doesn't help me to solve it.

While the above questions are all concerning the default, no-parameter constructor, I have a problem while using a two parameter constructor with default values - I tried to construct an object calling the constructor with only the first value given, and it is parsed as a function declaration instead of an object.

Here are some snippets of my code (I renamed the class names because they're long and not relevant):

class algoContainer{
public:
algoContainer(algo1Virtual &alg1 = algo1Concrete::emptyInstance(),
      algo2Virtual &alg2 = algo2Concrete::instance());

someUsefulFunction();
};

class algo1Concrete : public algo1Virtual{
    private:
    algo1Concrete();
    public:
    static algo1Concrete &emptyInstance(); // "empty" instance treated
                                           // specifically
                                           //  -- uses private no arg constructor
    algo1Concrete(const std::vector<data> &myData); // construcotr
};

class algo1Virtual{
    // ... all functions virtual, no implementations ...
};


// ... similar for algo2Virtual/Concrete ...

All the functions in the Concrete classes are implemented, while none of them in the Virtual classes are (except the constructors and the destructors).

So, my problem now is that I want to do something like:

std::vector <data> workData;
// fill workData
algoContainer myAC(algo1Concrete(workData));
myAC.someUsefulFunction(); // this line gives compile error

Nice, cute and ellegant, but it does not work (error the same as all the questions I linked). I've found this forum-tutorial that does refer to the problem as most vexing parse, but it's solution (put parenthesis around argument) doesn't solve the problem (it's a long bunch of error msgs in that case, but I can edit it in the question later if it helps - those are all related to inheriting a virtual function).

I've tested my code if I use the constructor with all the parameters on default, and even if I just construct the first parameter separately:

std::vector <data> workData;
// fill workData
algo1Concrete myA1(workData);
algoContainer myAC(myA1);

myAC.someUsefulFunction(); // now it works fine

algoContainer myAC2;
myAC2.someUsefulFunction(); // this also works

I can use the code as it is, but it would be greatly appreciated if somebody could give me a more elegant solution to the one I'm using right now.


EDIT: error msgs I get when I fix the most vexing parse

If I use the code with parenthesis:

algoContainer myAC((algo1Concrete(workData)));

My errors are:

/some_path/main.cpp:47:65: error: no matching function for call to ‘algoContainer::algoContainer(algo1Concrete)’
/some_path/main.cpp:47:65: note: candidates are:
/some_path/algo/algocont.h:45:5: note: algoContainer::algoContainer(algo1Virtual&, algo2Virtual&)
/some_path/algo/algocont.h:45:5: note:   no known conversion for argument 1 from ‘algo1Concrete’ to ‘algo1Virtual&’
/some_path/algo/algocont.h:36:7: note: algoContainer::algoContainer(const algoContainer&)
/some_path/algo/algocont.h:36:7: note:   no known conversion for argument 1 from ‘algo1Concrete’ to ‘const algoContainer&’

I renamed the paths and inserted the example file and class names (the same as above) for readability. Just a remark: line 45 is the definition of the constructor in question. On the other hand, line 36 is the line class algoContainer.

I also tried with this code:

algoContainer myDect((algo1Virtual)(algo1Concrete(workData)));

And then the errors are completely different:

/some_path/main.cpp:47:86: error: cannot allocate an object of abstract type ‘algo1Virtual’
/some_path/algo/alg1/algo1virtual.h:31:7: note:   because the following virtual functions are pure within ‘algo1Virtual’:
/some_path/algo/alg1/algo1virtual.h:42:8: note:     virtual algo1Virtual::~algo1Virtual()
/some_path/algo/alg1/algo1virtual.h:39:18: note:    virtual void algo1Virtual::someAlgo1Function(std::vector<data>&)
/some_path/main.cpp:47:87: error: no matching function for call to ‘algoContainer::algoContainer(algo1Virtual)’
/some_path/main.cpp:47:87: note: candidates are:
/some_path/algo/algocont.h:45:5: note: algoContainer::algoContiner(algo1Virtual&, algo2Virtual&)
/some_path/algo/algocont.h:45:5: note:   no known conversion for argument 1 from ‘algo1Virtual’ to ‘algo1Virtual&’
/some_path/algo/algocont.h:36:7: note: algo1Virtual::algo1Virtual(const algo1Virtual&)
/some_path/algo/algocont.h:36:7: note:   no known conversion for argument 1 from ‘algo1Virtual’ to ‘const algo1Virtual&’

Hope this helps.

like image 228
penelope Avatar asked Mar 19 '12 14:03

penelope


2 Answers

The issue seems to be due to the arguments taken by the constructor:

algoContainer( algo1Virtual &alg1,
               algo2Virtual &alg2 );

Note: I have removed the default arguments for brevity.

this takes the arguments as non-const references. So, when you make a call like:

algoContainer myAC(algo1Concrete(workData));

the construction of:

algo1Concrete(workData)

leads to the construction of an anonymous temporary. Anonymous temporaries cannot bind to non-const references, simple because they are temporary and any changes you might make to them will instantly go away (thats not the real reason, but seems to make sense. It doesn't mean anything to modify an anonymous temporary as you have no way to use it later (no name) or eventually (its temporary)). Actually, non-const references can only bind to l-values, and anonymous temporaries are r-values. (Details: Non-const reference may only be bound to an lvalue)

In general, this kind of use means that one wants to give complete ownership of the object being constructed to the function. This can be done by either passing-by-value (expensive), or in C++11, by passing by rvalue reference.

Passing by value will look like:

algoContainer( algo1Virtual alg1,
               algo2Virtual alg2 );

This will result in unnecessary copies.

The other option is to pass by rvalue reference in C++11 like:

algoContainer( algo1Virtual &&alg1,
               algo2Virtual &&alg2 );

Now your first usage will work out of the box:

std::vector <data> workData;
// fill workData
algoContainer myAC(algo1Concrete(workData));
myAC.someUsefulFunction();

but you second usage will need to be modified so that your object is "moved" into the constructor, and the algoContainer takes ownership of the data (the names local variable is then "bad" and should NOT be used at all.

std::vector <data> workData;
// fill workData
algo1Concrete myA1(workData);
algoContainer myAC(std::move(myA1)); //NOTICE THE std::move call.
//myA1 is now a dummy, and unusable as all the internals have gone.
myAC.someUsefulFunction(); 

For this above example to work, you will have to implement a move constructor for algo1Concrete with the following signature:

algo1Concrete ( algo1Concrete&& other )

which will simply transfer the internals to the current and leave the "other" in an undefined state. (Details: http://msdn.microsoft.com/en-us/library/dd293665.aspx)

NOTE: Regarding default arguments.

I generally suggest that default arguments to functions be avoided, as they lead to more confusion than convenience. All default parameters can be "simulated" simply by overloading the function. Thus, in your case, you would have three ctors:

algoContainer(); //This assumes that the args were both the statics
algoContainer( algo1Virtual alg1 ); //This assumes that arg2 was the static.
algoContainer( algo1Virtual alg1, algo2Virtual alg2 ); //This uses both input.

I agree its more verbose, and not a lot of compilers currently implement inheriting constructors, so we also quite often have copied code. But this will isolate one from a number of debugging/magic value issues which pop up while investigating an issue. But, FWIW, its just an opinion.

like image 194
Akanksh Avatar answered Sep 21 '22 17:09

Akanksh


algoContainer myAC(algo1Concrete(workData));

This line is illegal. You cannot bind an rvalue to a mutable lvalue reference. It must be const- and even if it was, the object would die before it could be used by any function except the constructor.

like image 26
Puppy Avatar answered Sep 23 '22 17:09

Puppy