C++ declares a function instead of calling a complex constructor
Asked Answered
N

4

6

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.

Nertie answered 19/3, 2012 at 14:38 Comment(6)
Can you tell us the error you get from algoContainer myAC((algo1Concrete(workData))); with the disambiguating parenthesis?Atropos
As Kerrek mentioned, this is a Most Vexing Parse issue. If you're getting errors when you disambiguate with parens, the error messages will be necessary to help solve the problem.Roy
Please see: en.wikipedia.org/wiki/Most_vexing_parseFantasist
@Fantasist I know it's a long question, but the last link I mentioned explains the most vexing parse in detail, I knew about it when asking the question. I never explicitly used the words "most vexing parse" in my Q, but now I've edited that in and added it in the tags for the question.Nertie
@Nertie Sorry about being preemptive with the link. I think the issue might be due to the algoContainer constructor taking inputs as non-const references, which cannot bind to anonymous temporaries. You would need named variables, or take arguments by rvalue reference, and "move" the algoConcrete into it. Essentially, think about who "owns" the arguments, and what their lifetimes are supposed to be.Fantasist
@Fantasist I think your comment is actually the most useful here. I just looked up rvalue references and std::move and it was what I was looking for... Unfortunately, I can't use it because the group library is still not using C++11. If you could type up what you just said in you last command nicely as an answer, I'll accept it.Nertie
F
3

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.

Fantasist answered 20/3, 2012 at 15:13 Comment(1)
when i asked you to expand a little, i didn't even dream about this :) this is awesome, thanks :DNertie
W
3

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.

Weirdie answered 19/3, 2012 at 14:52 Comment(6)
+1 you spotted the problem that crops up once the most vexing parse is fixed, even without the error message.Atropos
This seems right on. You must either take the argument by value or (as DeadMG said) by const&.Titanism
@DeadMG I think I do understand what you are saying... And I know the line is illegal, it's what got me to post a question. But the thing is, the way I wish to use this code, the constructed algo1Concrete is needed and used only by and in the algoContainer it is constructed for, BUT the algo1Concrete object is being changed during usage. I'm interested in another way I could do this in only 1 line.Nertie
@Nertie You could pass by value into the constructor. Without knowing the specifics of what algoContainer's constructor does there may be other alternatives as well.Atropos
@penelope: You have little choice because of the lifetime. You must either dynamically allocate it, or make it on the stack for as long as the algoContainer lives.Weirdie
@DeadMG unfortunately, I think you're right. I'm gonna sleep on it, see if there are any useful comments and suggestions in the morning and then decide how best to change my codeNertie
F
3

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.

Fantasist answered 20/3, 2012 at 15:13 Comment(1)
when i asked you to expand a little, i didn't even dream about this :) this is awesome, thanks :DNertie
R
0

Write this:

algoContainer myAC((algo1Concrete(workData)));

Then search the internet for "most vexing parse". There are also hundreds of duplicates of this question here on StackOverflow, but they're hard to find, because the problem is never identified in the question itself. (If it were, there'd be no question.)


(After your edit:) Temporary objects (such as those created by a function call that returns by value) do not bind to non-constant references. You need to say:

algo1Concrete ac = algo1Concrete(workData);
algoContainer myAC(ac);
Refract answered 19/3, 2012 at 14:45 Comment(2)
The OP already said that this resulted in a bunch of other errors (although I would expect the parens to be the solution).Atropos
@Kerrek SB I have already included a link in my question explaining the most vexing parse, as I knew about it when I posted it. I didn't explicitly use the words "most vexing parse" in the Q, but I edited those words in now. But still, that is not my main problem.Nertie
R
0

As alternatives to the current solution, you can use an extra pair of parentheses, or you can use copy initialization.

Regardful answered 19/3, 2012 at 14:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.