1

I wrote a program today which was giving me a real headache when it didn't work how I expected it to.

So I've written a short example which (almost)* reproduces the problem.

I hope this is fairly self-explanatory in what it is supposed to do.

#include <iostream>

class A
{
    public:

    enum class defaults
    {
        DEFAULT_A
    };

    A(defaults default_val)
    {
        if(default_val == defaults::DEFAULT_A)
        {
            A("hello world");
        }
    }

    A(std::string str_)
    {
        str = str_;
        flag = true;
    }

    std::string getStr()
    {
        return str;
    }

    bool getFlag()
    {
        return flag;
    }

    private:

    bool flag;
    std::string str;

};

int main()
{

    A a(A::defaults::DEFAULT_A);

    std::cout << a.getStr() << std::endl;
    std::cout << a.getStr().size() << std::endl;
    if(a.getFlag())
    {
        std::cout << "true" << std::endl;
    }
    else
    {
        std::cout << "false" << std::endl;
    }

}

My compile and run code: g++ --std=c++14 main.cpp && ./a.out

Compiling on/with gcc version 5.2.1 20151010 (Ubuntu 5.2.1-22ubuntu2)

Output: [with line numbers]

1: 
2: 0
3: true

(That first line is a blank line.)

*The only thing which is different from this example and the code I was working on today is that the flag in the code I wrote earlier was false not true, but this is still NOT what I expected it to be - I expected it to be true. And also, the string wasn't blank, but it did contain "nonsense values" - at least not what I expected it to contain.

What have I done wrong here? My guess is it's something really obvious, but I just can't spot it. (Perhaps I've been working on the same problem for too long now?)

Edit:

Can I do this to correct the problem?

A(defaults default_val)
{
    if(default_val == defaults::DEFAULT_A)
    {
        *this = A("hello world");
    }
}

4 Answers 4

3

To call a constructor from another constructor (a delegating constructor), you need to do it in the initializer list of the constructor:

       A(defaults default_val) : A("hello world") {
           .. more stuff to do after alling the delegate ctor

The problem is that this just allows you to make an unconditional call of the delegate ctor (though you can use ?: in the argument(s) to have their value be conditional.) There's no way to conditionally call the delegate.

Sign up to request clarification or add additional context in comments.

5 Comments

What I didn't tell you what that there's a bunch of if statements making decisions about what to do when the default value is something else, so this solution won't work.
@user3728501: Based on what you're saying, you seem to have a design problem in your code and are trying to force your way through when you should take a few steps back and reconsider refactoring your code instead.
@ray Please read info provided with other answers or in my question for how the problem has been solved / could be solved.
@user3728501: I did before posting. If it "has been solved", then you should clearly indicate that by accepting an answer. Still, my comment is based on your comment above, which suggests data coupling is present (i.e. trying to control the internal code path of a class/function based on some special value/flag).
@ray Read the part in my question about how it was solved - you clearly haven't
2

If you always want to call the other constructor, you can move the logic that figures out the correct argument into another function.

Then you can use a delegating constructor as follows:

#include <cassert>
#include <iostream>
#include <string>

struct A
{
    enum class defaults { first, second };

    A(defaults default_)
        : A(DefaultToString(default_))
    { }

    A(const std::string & str)
        : s_(str)
    { }

    static std::string DefaultToString(defaults default_)
    {
        switch (default_)
        {
        case defaults::first:   return "first";
        case defaults::second:  return "second";
        }

        assert(false);
    }

    std::string s_;
};

int main()
{
    A a1(A::defaults::first);
    A a2(A::defaults::second);
    A a3("other");
    std::cout << a1.s_ << " " << a2.s_ << " " << a3.s_ << "\n";
    return 0;
}

Comments

1
A(defaults default_val)
    {
        if(default_val == defaults::DEFAULT_A)
        {
            A("hello world");
        }
    }

If this if statement is true, you're not calling this object's other constructor.

Here, A("hello world") constructs a new temporary object, using the given this constructor ... which is immediately destroyed, because this is a temporary object.

6 Comments

How can I do what I am trying to do?
Also, a.getFlag() causes undefined behaviour because flag was not initialized
@user3728501 you could make a different member function that applies a string to A, and then both of your constructors call that.
I was about to mention the undefined behavior. What you have to do is refactor all of your constructors, introducing a new constructor that initializes all of the class members, and have your existing constructors invoke it as a nested constructor. That's the general idea.
Yes, the edit will work. This is not really the most efficient way of doing this, but it'll work.
|
0

You may use helper function for your delegating constructor:

namespace {
    A MakeA(defaults default_val)
    {
        return (default_val == defaults::DEFAULT_A) ?
            A("hello world") :
            A(/* other constructor*/);
    }
}

A::A(defaults default_val) : A(MakeA(default_val)) {}

As currently, you create a unused temporary.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.