Home > database >  Defining "hidden" constants in c header-only libraries
Defining "hidden" constants in c header-only libraries

Time:03-06

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?

CodePudding user response:

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.

CodePudding user response:

C header files are just "copy-pasted" into the source code by pre-processor.

So one way to hide these from where the header is included would be to use pre-processor #define for these constants. Note that using #define in modern C is considered a bad practice, and it should be used only when it really makes the code clearer and easier to maintain. Is this such a case? That's quite subjective, but good or bad, this still is one way to achieve what you want, and it is simple and easy to understand.

mylib.h:

#ifndef MYLIB_H
#defein MYLIB_H

// local constants of library, undefined at end
#define MYLIB_CUSTOM_PI (3.2)
// end of local constant defines

//... rest of the code

// undefine local constants
#undef MYLIB_CUSTOM_PI

#endif // MYLIB_H

This has the downsides of using pre-processor macros, but since you are using them only locally and not exposing them to the user of your library, it should work fine. This works well with primitive types only (including "string literal" which is const char* and compiler will elide them). For example STL container values shouldn't be defined with a macro (because a new container will be constructed at every place where the macro is used).

  • Related