Search code examples
c++constructorreferenceclass-designconst-reference

How can I make a Class with reference data member constructible with no arguments?


I have a class, say C, where one of the member data, say X, depends on user input. The user input may differ on every run, and in my current design, all instances of my classes stores a reference to the same object X.

How can I tweak the design so that it allows default constructor with no arguments?

This way I can use copy assignment/copy constructor, make arrays of C, use temporary rvalue etc.

Below is a minimal working example illustrating my question. In the case I work with, Tag refers to some external resources.

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

using namespace std;

struct Tag {
    int N;
    string tag;
};

template<typename T>
struct Vec {
    const Tag& tag;
    T* vec;

    Vec(const Tag& tag_) : tag(tag_) {
        vec = new T[tag.N];
    }

    ~Vec() {
        delete [] vec;
    }
};

Tag make_tag(vector<string>& args) {
    assert(args.size() == 3);
    int N = stoi(args[1]);
    return Tag {N, args[2]};
}

vector<string> arguments(int argc, char* argv[]) {
    vector<string> res;
    for(int i = 0; i < argc; i++)
        res.push_back(argv[i]);
    return res; 
}

int main(int argc, char* argv[]) {
    vector<string> args = arguments(argc, argv);
    Tag tag0 = make_tag(args);
    Tag tag1;
    Vec<double> vec(tag0);
    return 0;
}

Solution

  • How can I tweak the design so that it allows default constructor with no arguments?

    Well, three options that I would suggest:

    • The fancy way: Use a std::optional<std::reference_wrapper<T>> member. std::reference_wrapper is for putting reference in place where you're not sure a reference would just work as-is. std::optional<T> holds either a T or a nullopt (i.e. no-value). This has the benefit of the default initializer for the optional being argument-less, so you can possibly use a default constructor for the argument-less case of C.

    • The old-school way: Use a plain pointer member instead of the reference. Initialize it to nullptr on no-argument construction. (@RemyLebeau also suggested this in a comment.)

    • The smart-ass RAII way: Replace your class C with an std::optional<C>. This means that argument-less construction doesn't actually construct a C - it just keeps a nullopt, delaying the actual construction for later. This solution is relevant when you must maintain the invariant of C holding resources throughout its existence; it also has the benefit of keeping the reference member of C as const.

    I purposely am not considering your MWE (you said it is just illustrative), and am offering a more general answer.