0

The following code doesn't compile because the instance parameters don't match the constructor parameters. If I remove the constructor it compiles and runs. I would like to be able to use it in either way - constructing with string or direct member variable initialisation. (This is a minimal version of what I'm really trying to do.) Do I need another constructor with an initialiser list or similar?

Specifically, I don't want to add another constructor with two ints, I want to use the mechanism that is used if I delete the string constructor.

#include <iostream>
#include <string>
struct S
{
    int m_a;
    int m_b;
    
    S(const std::string s):
        m_a(99),
        m_b(99)
    {std::cout << "ctor runs" << std::endl;}
    
    friend std::ostream& operator<<(std::ostream& os, const S& s)
    {  os << "S: " << s.m_a << ", "  << s.m_b; }
};

int main()
{
    S s{1,2};
    std::cout << s << std::endl;
}
3
  • 3
    If you remove the S(const std::string s) constructor, and instead do a static S make(const std::string s) { return S{99, 99}; } class factory function, you can have the aggregate construction behavior you want, without writing a constructor with two ints. Commented Feb 25, 2021 at 14:58
  • Something like that to have only one constructor template <typename ... Ts> S(Ts&&... args) : m_a(sizeof...(Ts) == 2 ? std::get<0>(std::tie(args...)) : 99), m_a(sizeof...(Ts) == 2 ? std::get<1>(std::tie(args...)) : 99) { if (sizeof...(Ts) == 1) { std::cout << "ctor runs\n"; } } :) (need also to protect against copy/move constructor, or check types). (Don't do that). Commented Feb 25, 2021 at 15:01
  • I love the suggestion Elijay but the 'string' version is for the normal user so I want it simple and the fixed data is for my test code. Commented Feb 25, 2021 at 15:02

2 Answers 2

1

What you want is not possible. Your options are:

  1. Don't define constructors and use aggregate initialisation.
  2. Define constructor for each set of arguments you want and implement them to behave exactly as you want to.

The options are mutually exclusive.

Do I need another constructor with an initialiser list or similar?

You could define a constructor accepting a std::initializer_list. It would be worse than defining a constructor accepting two ints.

I want to use the mechanism that is used if I delete the string constructor.

You will be able to use that "mechanism" by deleting the string constructor.


P.S. You should typically avoid passing strings by value.

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

8 Comments

Worse in what way?
In this very trivial example I would add the second constructor but the actual struct is much more complex and I already have the data in that format.
@NathanPierson The compiler cannot check for correctness if you pass a number of ints other than two. Another problem that doesn't apply to ints, but is an issue if generalised, is that initialiser_lists are copied which can be slow for some types and not allowed for others.
@NathanPierson: S s{4, 8, 15, 16, 23, 42}; won't make sense.
@Ant Having a more complex class doesn't change your options. They remain the same: Either don't define any constructor, or define one for each set of parameters that you want.
|
1

Once you add a user provided constructor, your class is no longer an aggregate. That means you can't directly construct the object with

S s{1,2};

unless you have a suitable constructor defined. In this case it's as simple as adding

S(int a, int b) : m_a(a), m_b(b) {}

and now an S can be made via two int's or anything that can be implicitly converted to int.

Specifically, I don't want to add another constructor with two ints, I want to use the mechanism that is used if I delete the string constructor.

Put simply, that's what the constructor that takes two int's does. It allows you to create the object as if it was an aggregate, even though it is not. That said, to get the exact same functionality, you'd need to provide default values like

S(int a = 0, int b = 0) : m_a(a), m_b(b) {}

and now

S a{};
S b{1};
S c{1, 2};

all compile but

S d{1, 2, 3};

wont.

1 Comment

The OP states that – Specifically, I don't want to add another constructor with two ints.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.