10
\$\begingroup\$

Being a C++ developer, but also having been influenced by functional languages, I strongly believe that const should be the default for the vast majority of all variables when they are created and passed as arguments.

C++ does provide a const keyword, but it is not "by default". Many people have asked questions about this, and the classic answer it "you would need to invent another language, but C++ is here to stay".

I tried to find a different solution, and I would like to have some experts review my attempt.

const_initialize

const_initialize aims to make it easy to instantiate a const variable, even when its initialization is complex. This is inspired by an article from Herb Sutter.

This example shows the idiomatic C++14 of initializing a complex const variable through a lambda:

//construct and initialize vec of type const std::vector<int>
const auto vec = [&]() { //define a lambda that initializes the vector
std::vector<int> _vec;
for (unsigned i = 0; i < 10; i++)  
  _vec.push_back(i);  
return _vec;
}(); //run the lambda immediately

const_initialize is an attempt to make this code shorter and easier to read. With this macro, the previous code can be rewritten as:

const_initialize( std::vector<int>, vec,  // here, we define the type and name of the const variable.
  for (unsigned i = 0; i < 10; i++)  //initialization code
    vec.push_back(i);
);  

const_initialize:

#define const_initialize(type, name, initialization_code) \
const auto name = [ & ]()                 \
{                                         \
  type name;                              \
  initialization_code;                    \
  return name;                            \
}();

const_typedefs

This one is more controversial. I think that const ought to be the default, and we should not spend time reading and writing const in any given source code.

So, the philosophy is, for any given class Footo make its const variants very easy and fast to read and write. It defines the following types:

  • _Foo is a typedef for const Foo
  • _Foo_ is a typedef for const Foo &
  • Foo_is a typedef for Foo &

Example:

const_typedefs_ns(std, string);
/*
After this call :
std::_string <-> const std::string
std::_string_ <-> const std::string &
std::string_ <-> std::string &
*/

void showString(std::_string_ msg) // const std::string &
{
  std::cout << msg;
}

int main()
{
  std::_string msg{"hello"}; //const std::string
  showString(msg);
}

const_typedef:

#define const_typedefs(MyType) \
  typedef const MyType _##MyType; \
  typedef const MyType & _##MyType##_;\
  typedef MyType &  MyType##_;  

//namespacify is just a helper macro
#define namespacify(namespce, _what) \
  namespace namespce \
  { \
    _what \
  }\

//const_typedefs_ns : is const_typedefs inside a namespace
#define const_typedefs_ns(namespce, MyType) namespacify(namespce, const_typedefs(MyType))

I have posted sample code on GitHub.

What are your thoughts?

\$\endgroup\$
4
  • \$\begingroup\$ Quick comment: The leading underscore that const_typedefs adds has a high risk of conflicting with names that are reserved for use by the compiler. \$\endgroup\$
    – Bart van Ingen Schenau
    Sep 27, 2015 at 16:12
  • \$\begingroup\$ Indeed. I searched for other possibilities, but could not find better. May be a prefix such as "c_" \$\endgroup\$
    – Pascal T.
    Sep 27, 2015 at 16:21
  • \$\begingroup\$ You might find this of interest: michael.gr - Why the 'final' (Java) or 'readonly' (C#) keyword is a bad idea \$\endgroup\$
    – Mike Nakis
    Sep 27, 2015 at 18:10
  • 1
    \$\begingroup\$ My thought is that you are proposing obfuscating a perfectly adequate way of showing const. However clever your solution it will be something that needs to be learnt by a newly hired programmer that could have been avoided by using const. Save the cleverness for the problem domain. \$\endgroup\$
    – Ant
    Sep 29, 2015 at 16:18

1 Answer 1

3
\$\begingroup\$

Ask yourself this: "Do I really need a macro?" Then clear your mind of all thoughts. Think about puppies. Meditate. After meditating for a while and all of your troubles have washed away, ask again: "Do I really need a macro?" If your answer is yes, you probably need a vacation. Maybe somewhere in the Bahamas.

In times like these I like to bring up some salient points:

  • Herb Sutter is not a God, although on occasion he's deserving of the title

  • Macros are almost always bad. They obfuscate code, create horrible compiler errors, and are difficult to maintain

  • C++11 features are cool, but should only be used where appropriate, rather than shoehorned into every nook and cranny possible

  • const correctness does not imply "use const everywhere"

So what is the solution:

  • Recognize that this cute trick is only applicable to this one scenario, and shouldn't be generalized without good reason

  • Lambdas are just syntactic sugar for functors. The usual caveats apply

Future maintainers will thank you.

Now, some commenters on the article believe that the original asker is actually talking about lazily initializing a const variable. In that case, there's something that's much more terser and clearer:

const int i = (someCondition) ? calc() : default_value;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.