Uduse
Uduse

Reputation: 1581

How to resolve this call to ambiguous constructor by making one of the must be explicit?

In the following code, I construct an instance of Node by passing an instance of Point. e.g. Node{{0, 1}}; However, the call to Node's constructor is ambiguous. My guess it that {0, 1} can also be comprehended as two int representations of char, and {char, char} also applies to the constructor of std::string, in the form of std::initializer_list<char>.

I want to preserve the ability to construct Node conviniently as Node{{0, 1}} while also retain the conversion from std::string to Node, preferebly in the form of Node{std::string{"0,1"}} or something like that. Is there a way to call the constructor that takes a string only if I specifies it to do so? or simply disable the std::string's constructor of std::initializer_list<char>?

Thanks in adavance.

#include <string>

class Point
{
public:
    Point() {};
    Point(int x, int y) : x(x), y(y) {}
private:
    int x;
    int y;
};

class Node
{
public:
    Node(const std::string& str) {}
    Node(const Point& dot) : dot(dot) {}
private:
    Point dot;
};

int main()
{
    Node{{0, 1}};
    return 0;
}

Error Message:

/Users/cpp_sandbox.cpp:24:5: error: call to constructor of 'Node' is ambiguous
    Node{{0, 1}};
    ^   ~~~~~~~~
/Users/cpp_sandbox.cpp:16:5: note: candidate constructor
    Node(const std::string& str) {}
    ^
/Users/cpp_sandbox.cpp:17:5: note: candidate constructor
    Node(const Point& dot) : dot(dot) {}
    ^
1 error generated.
[Finished in 0.1s with exit code 1]
[shell_cmd: g++ -std=c++11 "/Users/cpp_sandbox.cpp" -o "/Users/cpp_sandbox"]
[dir: /Users]
[path: /usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/opt/X11/bin:/Applications/SquishCoco/:/Library/TeX/texbin]

Upvotes: 4

Views: 1526

Answers (1)

vsoftco
vsoftco

Reputation: 56547

As mentioned in the comments, you're out of luck with your current code as both constructors are viable candidates. A solution is to use inheritance instead of aggregation and have Node derive from Point. You must also explicitly inherit Point's constructor:

class Node: public Point
{
public:
    using Point::Point; // constructor inheritance
    Node(std::string str) {}
};

Now you can use

Node{0, 1}; // note that Node{{0, 1}} will invoke the string ctor

and

Node{"test"};

just fine.

Full example:

#include <iostream>
#include <string>

class Point
{
public:
    Point() {};
    Point(int x, int y) : x(x), y(y) {std::cout << __PRETTY_FUNCTION__ << '\n';}
private:
    int x{};
    int y{};
};

class Node: public Point
{
public:
    using Point::Point;
    Node(const std::string&) {std::cout << __PRETTY_FUNCTION__ << '\n';}
};

int main()
{
    Node{0, 1};
    Node{"test"};
}

Live on Coliru

If you wonder why now Node{{0, 1}} will invoke the Node::Node(const std::string&) constructor instead of an ambiguity, that's because the only viable constructor candidate is Node::Node(const std::string&) (the other inherited constructor has the signature Point::Point(int, int), so it won't accept an initializer list). Whereas in your original code Node::Node(const Point&) would also be a viable constructor due to the implicit conversion from an initializer list to a Point (this kind of constructor is called a conversion constructor).

Upvotes: 4

Related Questions