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 int
s 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.