Search code examples
c++constantsclangc++17header-files

Defining "hidden" constants in c++ header-only libraries


I'm creating a C++ header-only implementation of a library in which I defined multiple constants (defined as static constexprs) that I only want to use in the library, to make my code more clear and readable. I thought having internal linkage would be enough to "hide" these constants from any other source files in which I include this library.

However, after including this header in another file where I am writing unit tests for it, I'm getting errors like redefinition of '<constant_name>' (the test source file has its own constants with the same name since they would be useful in some of the test cases, also defined as static constexprs). After some further reading, I now understand that internal linkage means the constants will not be available outside the translation unit, but the translation unit includes the source file as well as any headers it includes. This would explain why I'm getting that redefinition error, as constants with the same name exist in both the test source file and the library header file, and they end up being a single translation unit.

Currently, I have wrapped said constants in a namespace to avoid the clashing. However, this is not ideal. For example, I still get autocomplete suggestions for these constants in other files when I prefix with the namespace. I would instead like them to be completely hidden.

My question is regarding whether there is any way around this. Is there some way to make these constants truly visible only within the header itself, and invisible to any of the files which include it?

EDIT: Here's some code to demonstrate what I mean.

library.hpp

static constexpr int USEFUL_CONSTANT = 5;

namespace library {
    ...library implementation here...
}

tests.cpp

#include "library.hpp"

// Constant happens to be useful in tests too, but I don't 
// want to expose it for everybody, so I redefine it here
static constexpr int USEFUL_CONSTANT = 5;

...tests here...

I get the redefinition error since both constants would be part of the same translation unit.


Solution

  • A couple of viable approaches:

    1. inner namespace, (boost-style)
    namespace mylib
    {
        namespace detail
        {
            inline constexpr int const goodenough{42};
        }
        
        int foo(void)
        {
            return detail::goodenough;
        }
    }
    
    1. conversion into private static fields of some class with access granted by friend declaration
    namespace mylib
    {
        int foo(void);
    
        class detail
        {
            friend int ::mylib::foo(void);
     
            static inline constexpr int const goodenough{42};
        };
        
        int foo(void)
        {
            return detail::goodenough;
        }
    }
    

    Note: constexpr implies inline and const.