0

I have a class C having member variables such that each, when not set, must be set to a default value. I have a lot of member variables in C, and various constructors that set some variables and do not set others etc, so that to be sure that the member variables that must have a default values are set with their default values, I rely on an void Init() member function : in Init I set all member variables having default values to their default values, and I call Init in my constructors etc.

Now, I have to refer to the default values later in my code, typically to know if through a setter the client set them to something else than their default value or not, so that I can trigger one behaviour or another.

My question is : what is the best way to implement the concept "default value for a member variable" ? Through a constant defined in the header declaring C ? As a const member variable ? As static const member variable ?

Remark : I am allowed to c++ <= 2003.

Olórin
  • 3,367
  • 2
  • 22
  • 42

3 Answers3

0

If you have "various constructors" then you have to add the initialization of the member variables that are not initialized in the code of the contructors in the initializer lists.

If this feels cumbersome you can factor out this in a common initializer function.

Modern C++ allows you constructor inheritance where one constructor can call a (base) constructor like here.

Community
  • 1
  • 1
harper
  • 13,345
  • 8
  • 56
  • 105
0

One way to store your default values would be as a constexpr static instance of a specially crafted LiteralType, allowing you to check against its members. Any member variables that can be constructed at compile time are easy enough to store, but you would need to make a constexpr constructible wrapper for run-time only types such as std::string. Then, when the compiler is told to optimise the code, it should be able to remove the "default values" instance entirely, unless you take its address or do something of the sort.

If, for example, you have a class, CQQC (name chosen for brevity and uniqueness), that stores three ints and a std::string, with default values 0, 42, 359, and "Hey, y'all!", you could do something like this:

// Forward declaration for our "default values" class.
class CQQC;

namespace detail {
    // String wrapper, convertible to std::string.
    class literal_string {
        const char* const str;
        size_t sz; // Currently not needed, but useful if additional functionality is desired.

        static constexpr size_t length(const char* const str_) {
            return *str_ ? 1 + length(str_ + 1) : 0;
        }

      public:
        template<size_t N>
        constexpr literal_string(const char (&str_)[N])
          : str(str_), sz(N - 1) { }

        constexpr literal_string(const char* const str_)
          : str(str_), sz(length(str_)) { }

        operator std::string() const {
            return std::string(str);
        }
    };

    // Generic "default values" type, specialise for each class.
    template<typename>
    struct Defaults_;

    // Defaults for CQQC.
    template<>
    struct Defaults_<::CQQC> {
        int i, j, k;
        literal_string str;

/*
        template<size_t N = 12>
        constexpr Defaults_(int i_ = 0,
                            int j_ = 42,
                            int k_ = 359,
                            const char (&str_)[N] = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
*/

        constexpr Defaults_(int i_ = 0,
                            int j_ = 42,
                            int k_ = 359,
                            const char* const str_ = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
    };
} // namespace detail

// Boilerplate macro.
#define MAKE_DEFAULTS(Class) \
    constexpr static ::detail::Defaults_<Class> Defaults = ::detail::Defaults_<Class>()

Then, CQQC creates a compile-time static instance of its "default values" class (or emits an error, if the class isn't a LiteralType). It will then consult this instance whenever it needs to check its default values.

class CQQC {
    static_assert(std::is_literal_type<detail::Defaults_<CQQC>>::value,
                  "Default value holder isn't a literal type.");

    MAKE_DEFAULTS(CQQC);
    // Expands to:
    // constexpr static ::detail::Defaults_<CQQC> Defaults = ::detail::Defaults_<CQQC>();

    int i, j, k;
    std::string str;

  public:
    // Allows the user to specify that they want the default value.
    enum Flags { DEFAULT };

    // Initialise to defaults, unless otherwise specified.
    CQQC(int i_ = Defaults.i,
         int j_ = Defaults.j,
         int k_ = Defaults.k,
         std::string str_ = Defaults.str)
      : i(i_), j(j_), k(k_), str(str_) {}

    bool isDefault() {
        return (i   == Defaults.i &&
                j   == Defaults.j &&
                k   == Defaults.k &&
                str == Defaults.str.operator std::string());
    }

    void set_i(int i_) { i = i_; }

    // Set to default.
    void set_i(Flags f_) {
        if (f_ == Flags::DEFAULT) { i = Defaults.i; }
    }

    // And so on...
};
constexpr detail::Defaults_<CQQC> CQQC::Defaults;

See it in action here.


I'm not sure how common something like this is, but the nice thing about it is that it provides a generic interface for default values, while still allowing you to put each class' defaults holder in the class' header.

Using the above example:

// main.cpp
#include <iostream>
#include "cqqc.h"

int main() {
    CQQC c1(4);
    CQQC c2;

    if (c1.isDefault()) {
        std::cout << "c1 has default values.\n";
    } else {
        std::cout << "c1 is weird.\n";
    }

    if (c2.isDefault()) {
        std::cout << "c2 has default values.\n";
    } else {
        std::cout << "c2 is weird.\n";
    }

    c1.set_i(CQQC::DEFAULT);
    if (c1.isDefault()) {
        std::cout << "c1 now has default values.\n";
    } else {
        std::cout << "c1 is still weird.\n";
    }
}

// -----

// defs.h
#ifndef DEFS_H
#define DEFS_H

#include <string>

namespace detail {
    // String wrapper, convertible to std::string.
    class literal_string {
        const char* const str;
        size_t sz; // Currently not needed, but useful if additional functionality is desired.

        static constexpr size_t length(const char* const str_) {
            return *str_ ? 1 + length(str_ + 1) : 0;
        }

      public:
        template<size_t N>
        constexpr literal_string(const char (&str_)[N])
          : str(str_), sz(N - 1) { }

        constexpr literal_string(const char* const str_)
          : str(str_), sz(length(str_)) { }

        operator std::string() const {
            return std::string(str);
        }
    };

    // Generic "default values" type, specialise for each class.
    template<typename>
    struct Defaults_;
} // namespace detail

// Boilerplate macro.
#define MAKE_DEFAULTS(Class) \
    constexpr static ::detail::Defaults_<Class> Defaults = ::detail::Defaults_<Class>()

#endif // DEFS_H

// -----

// cqqc.h
#ifndef CQQC_H
#define CQQC_H

#include <string>
#include <type_traits>
#include "defs.h"

// Forward declaration for our "default values" class.
class CQQC;

namespace detail {
    // Defaults for CQQC.
    template<>
    struct Defaults_<::CQQC> {
        int i, j, k;
        literal_string str;

/*
        template<size_t N = 12>
        constexpr Defaults_(int i_ = 0,
                            int j_ = 42,
                            int k_ = 359,
                            const char (&str_)[N] = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
*/

        constexpr Defaults_(int i_ = 0,
                            int j_ = 42,
                            int k_ = 359,
                            const char* const str_ = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
    };
} // namespace detail

class CQQC {
    static_assert(std::is_literal_type<detail::Defaults_<CQQC>>::value,
                  "Default value holder isn't a literal type.");

    MAKE_DEFAULTS(CQQC);
    // Expands to:
    // constexpr static ::detail::Defaults_<CQQC> Defaults = ::detail::Defaults_<CQQC>();

    int i, j, k;
    std::string str;

  public:
    // Allows the user to specify that they want the default value.
    enum Flags { DEFAULT };

    // Initialise to defaults, unless otherwise specified.
    CQQC(int i_ = Defaults.i,
         int j_ = Defaults.j,
         int k_ = Defaults.k,
         std::string str_ = Defaults.str);

    bool isDefault();

    void set_i(int i_);
    void set_i(Flags f_);

    // And so on...
};

#endif // CQQC_H

// -----

// cqqc.cpp
#include "defs.h"
#include "cqqc.h"

// Initialise to defaults, unless otherwise specified.
CQQC::CQQC(int i_           /* = Defaults.i */,
           int j_           /* = Defaults.j */,
           int k_           /* = Defaults.k */,
           std::string str_ /* = Defaults.str */)
  : i(i_), j(j_), k(k_), str(str_) {}

bool CQQC::isDefault() {
    return (i   == Defaults.i &&
            j   == Defaults.j &&
            k   == Defaults.k &&
            str == Defaults.str.operator std::string());
}

void CQQC::set_i(int i_) { i = i_; }

// Set to default.
void CQQC::set_i(CQQC::Flags f_) {
    if (f_ == Flags::DEFAULT) { i = Defaults.i; }
}

constexpr detail::Defaults_<CQQC> CQQC::Defaults;

Not sure if this is a pattern, an anti-pattern, or what, but it provides a nice level of encapsulation, in that only code that needs to work with CQQC can see its default values.


With that done, we can now make this code backwards-compatible with C++03, without any major modifications, through the use of macros to conditionally enable <type_traits> and static_assert, and conditionally switch between constexpr and const, based on the value of __cplusplus. Note that even if this is done, any resulting C++03 code may not be as efficient as the C++11 equivalent, as the compiler may not optimise const variables out of the finished product as well as it optimises constexpr ones out.

To do this, we need to define a few constexpr helper macros, instead of using the keyword directly, and change the boilerplate macro for C++03 or earlier. (Since the latter needs to be changed, anyways, there's no need to use a helper macro in it.):

// constexpr helpers.
#if       __cplusplus >= 201103L
    #define CONSTEXPR_FUNC constexpr
    #define CONSTEXPR_VAR  constexpr
#else  // __cplusplus >= 201103L
    #define CONSTEXPR_FUNC
    #define CONSTEXPR_VAR  const
#endif // __cplusplus >= 201103L

// Boilerplate macro.
#if       __cplusplus >= 201103L
    #define MAKE_DEFAULTS(Class) \
        constexpr static ::detail::Defaults_<Class> Defaults = ::detail::Defaults_<Class>()
#else  // __cplusplus >= 201103L
    #define MAKE_DEFAULTS(Class) \
        const static ::detail::Defaults_<Class> Defaults;
#endif // __cplusplus >= 201103L

Then, we just need to wrap <type_traits> and static_assert() in #if __cplusplus >= 201103L ... #endif blocks, change Flags::DEFAULTS in CQQC::set_i(Flags) because enums don't introduce their own scope before C++11 (I just changed it to CQQC::DEFAULT, because I wanted to keep it scoped to clarify that it's not a macro), and take care of one or two tiny syntax issues (such as <::CQQC> being valid in C++11, but not C++03, which is fixed by adding a space), and voila:

// main.cpp
#include <iostream>
#include "cqqc.h"

int main() {
    CQQC c1(4);
    CQQC c2;

    if (c1.isDefault()) {
        std::cout << "c1 has default values.\n";
    } else {
        std::cout << "c1 is weird.\n";
    }

    if (c2.isDefault()) {
        std::cout << "c2 has default values.\n";
    } else {
        std::cout << "c2 is weird.\n";
    }

    c1.set_i(CQQC::DEFAULT);
    if (c1.isDefault()) {
        std::cout << "c1 now has default values.\n";
    } else {
        std::cout << "c1 is still weird.\n";
    }
}

// -----

// defs.h
#ifndef DEFS_H
#define DEFS_H

#include <string>

// constexpr helpers.
#if       __cplusplus >= 201103L
    #define CONSTEXPR_FUNC constexpr
    #define CONSTEXPR_VAR  constexpr
#else  // __cplusplus >= 201103L
    #define CONSTEXPR_FUNC
    #define CONSTEXPR_VAR  const
#endif // __cplusplus >= 201103L

namespace detail {
    // String wrapper, convertible to std::string.
    class literal_string {
        const char* const str;
        size_t sz; // Currently not needed, but useful if additional functionality is desired.

        static CONSTEXPR_FUNC size_t length(const char* const str_) {
            return *str_ ? 1 + length(str_ + 1) : 0;
        }

      public:
        template<size_t N>
        CONSTEXPR_FUNC literal_string(const char (&str_)[N])
          : str(str_), sz(N - 1) { }

        CONSTEXPR_FUNC literal_string(const char* const str_)
          : str(str_), sz(length(str_)) { }

        operator std::string() const {
            return std::string(str);
        }
    };

    // Generic "default values" type, specialise for each class.
    template<typename>
    struct Defaults_;
} // namespace detail

// Boilerplate macro.
#if       __cplusplus >= 201103L
    #define MAKE_DEFAULTS(Class) \
        constexpr static ::detail::Defaults_<Class> Defaults = ::detail::Defaults_<Class>()
#else  // __cplusplus >= 201103L
    #define MAKE_DEFAULTS(Class) \
        const static ::detail::Defaults_<Class> Defaults;
#endif // __cplusplus >= 201103L

#endif // DEFS_H

// -----

// cqqc.h
#ifndef CQQC_H
#define CQQC_H

#include <string>

#if       __cplusplus >= 201103L
    #include <type_traits>
#endif // __cplusplus >= 201103L


#include "defs.h"

// Forward declaration for our "default values" class.
class CQQC;

namespace detail {
    // Defaults for CQQC.
    template<>
    struct Defaults_< ::CQQC> {
        int i, j, k;
        literal_string str;

/*
        // This constructor won't work with C++03, due to the template parameter's default
        //  value.
        template<size_t N = 12>
        CONSTEXPR_FUNC Defaults_(int i_ = 0,
                                 int j_ = 42,
                                 int k_ = 359,
                                 const char (&str_)[N] = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
*/

        CONSTEXPR_FUNC Defaults_(int i_ = 0,
                                 int j_ = 42,
                                 int k_ = 359,
                                 const char* const str_ = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
    };
} // namespace detail

class CQQC {
#if       __cplusplus >= 201103L
    static_assert(std::is_literal_type<detail::Defaults_<CQQC>>::value,
                  "Default value holder isn't a literal type.");
#endif // __cplusplus >= 201103L

    MAKE_DEFAULTS(CQQC);
    // C++11: Expands to:
    // constexpr static ::detail::Defaults_<CQQC> Defaults = ::detail::Defaults_<CQQC>();
    // C++03: Expands to:
    // const static ::detail::Defaults_<CQQC> Defaults;

    int i, j, k;
    std::string str;

  public:
    // Allows the user to specify that they want the default value.
    enum Flags { DEFAULT };

    // Initialise to defaults, unless otherwise specified.
    CQQC(int i_ = Defaults.i,
         int j_ = Defaults.j,
         int k_ = Defaults.k,
         std::string str_ = Defaults.str);

    bool isDefault();

    void set_i(int i_);
    void set_i(Flags f_);

    // And so on...
};

#endif // CQQC_H

// -----

// cqqc.cpp
#include "defs.h"
#include "cqqc.h"

// Initialise to defaults, unless otherwise specified.
CQQC::CQQC(int i_           /* = Defaults.i */,
           int j_           /* = Defaults.j */,
           int k_           /* = Defaults.k */,
           std::string str_ /* = Defaults.str */)
  : i(i_), j(j_), k(k_), str(str_) {}

bool CQQC::isDefault() {
    return (i   == Defaults.i &&
            j   == Defaults.j &&
            k   == Defaults.k &&
            str == Defaults.str.operator std::string());
}

void CQQC::set_i(int i_) { i = i_; }

// Set to default.
void CQQC::set_i(CQQC::Flags f_) {
    if (f_ == CQQC::DEFAULT) { i = Defaults.i; }
}

CONSTEXPR_VAR detail::Defaults_<CQQC> CQQC::Defaults;

[Tested with GCC 5.3.1 20151207, and the C++03 one still has a symbol for Defaults in object files generated with -O3. Can't compare with MSVC right now, I don't have 2015 installed on this system and, since I don't know where online MSVC compilers store temporary object files, I can't dumpbin /symbols them online.]

literal_string::length() used from this question, because it was faster than writing my own.

Community
  • 1
  • 1
  • Very nice indeed but, as I wrote in my question, I am allowed to `c++ <= c++03`, and `constexpr` is `c++ >= c++11`... – Olórin Oct 31 '16 at 08:42
  • Oh, I missed that. Hold on, I'll see if it works with regular `const`, and rewrite it a bit if necessary. – Justin Time - Reinstate Monica Oct 31 '16 at 17:51
  • @user10000100_u ...And 1) Added a constructor to `literal_string` that takes a `const char* const`, and 2) Used a little preprocessor magic to make it C++03-compatible. (Note that it may be less efficient in C++03, depending on whether the compiler can optimise `static const` member variables out.) – Justin Time - Reinstate Monica Oct 31 '16 at 21:13
  • I will have a torough look at your code (thx again) and will come back to you ! – Olórin Nov 01 '16 at 22:40
0

Here is an ideea that works in C++03:

template <class T> struct Default_value
{
private:
  T value_;
  T default_value_;

public:
  Default_value(const T& default_value)
    : value_(default_value), default_value_(default_value)
  {}

  const T& get() const { return value_; }
  T& get() { return value_; }

  const T& get_default() const { return default_value_; }
  bool is_default() const { return value_ == default_value_; }
};

struct X_init {
  Default_value<int> a_, b_;
  Default_value<std::string> str_;

  X_init() : a_(24), b_(42), str_("This is sparta") {}

  X_init& set_a(int a) { a_.get() = a; return *this; }
  X_init& set_b(int b) { b_.get() = b; return *this; }
  X_init& set_str(const std::string& str) { str_.get() = str; return *this; } 
};

struct X {
  X_init values_;

  X() : values_() {}
  X(const X_init& values) : values_(values) {}

  //... X implementation
};
int main()
{
  X x = X_init().set_a(32).set_str("nope");

  cout << std::boolalpha;
  cout << "a: " << x.values_.a_.get() << " " << x.values_.a_.is_default() << endl;
  cout << "b: " << x.values_.b_.get() << " " << x.values_.b_.is_default() << endl;
  cout << "str: " << x.values_.str_.get() << " " << x.values_.str_.is_default() << endl;
}
a: 32 false
b: 42 true
str: nope false

You have to do more work, but it does exactly what you want.

Of course you can adapt and/or extend it to suit your needs.

The ideea is simple. We have a Default_value templated class. This allows us to explicitly set the desired default value and to track if the value was changed from the default value or not.

Then we have X_init a class specially designed to initialize members of X. The advantage is that you can chain the setters and so you can explicitly set some of the members while leaving the rest to default. This is known as named parameter idiom

The disadvantage to this method is that you have all data members of X bundled in a X_init class. If you don't like this you can incorporate X_init logic into X:

struct X {
  Default_value<int> a_, b_;
  Default_value<std::string> str_;


  X() : a_(24), b_(42), str_("This is sparta") {}

  X& set_a(int a) { a_.get() = a; return *this; }
  X& set_b(int b) { b_.get() = b; return *this; }
  X& set_str(const std::string& str) { str_.get() = str; return *this; } 
};

int main()
{
  X x = X().set_a(32).set_str("nope");

  cout << std::boolalpha;
  cout << "a: " << x.a_.get() << " " << x.a_.is_default() << endl;
  cout << "b: " << x.b_.get() << " " << x.b_.is_default() << endl;
  cout << "str: " << x.str_.get() << " " << x.str_.is_default() << endl;
}
bolov
  • 72,283
  • 15
  • 145
  • 224