An elegant way to avoid multiple templates that do not depend on template types

I have a base class that looks something like this:

template <typename TCode, TCode SuccessVal>
class Error
{
public:
    typedef TCode code_t;

    Error(TCode code, char const *descr="Unknown Error."):
        code(code), descr(descr)
    {

    }

    ...

    char const *description() const
    {
        return descr;
    }

    ...

private:
    TCode code;
    char const *descr;
};

      

All it does is encapsulate some sort of error code enumeration class so that it provides a little more context for logging.

Now let's say that I have a function panic

:

template <typename TCode, TCode SuccessVal>
void panic(Error<TCode, SuccessVal> const &e)
{
    puts("Panic!");
    printf("Unrecoverable error: %s", e.description());
    abort();
}

      

Compiling with -fdump-tree-original shows that in my case this results in several different functions with the same code. This is what you expect, but probably not what you would like.

The obvious route would be a base class that just had a message and a constructor accepting the message, but I find this rather unappealing.

We never use the error code, so nothing we do depends on T. How can I avoid a ton of template instances that compile to basically the same code?

Another desirable feature would be to make sure that whatever TCode type is, it is coercive for an integer type.

+3


source to share


2 answers


An obvious factorization of this code would be the following:

[[noreturn]] void panic_with_message(char const * msg)
{
    std::printf("Panic!\nUnrecoverable error: %s\n", msg);
    std::fflush(stdout);
    std::abort();
}

template <typename T, T Val>
[[noreturn]] static inline void panic(Error<T, Val> e)
{
    panic_with_message(e.description());
}

      



You can put just the template in the header along with the function declaration and keep the function definition in a separate translation unit. This should cause the code to bloat to a minimum:

// panic.hpp

#ifndef H_PANIC
#define H_PANIC

#include "error.hpp"  // for Error<T, Val>

[[noreturn]] void panic_with_message(char const * msg);

template <typename T, T Val>
[[noreturn]] static inline void panic(Error<T, Val> e)
{
    panic_with_message(e.description());
}

#endif

      

+6


source


Another option is to use extern template declarations. The C ++ 11 and older compilers for Visual Studio support this (as a language extension). Assuming you have a small enough number of Error instances, you can define an extern template declaration for each one and put the actual function in an instance of a single translation unit. This will ensure that you only have one instance for each panic option.

panic.hpp

#ifndef PANIC_HPP
#define PANIC_HPP

template <typename TCode, TCode SuccessVal>
struct Error
{
    const char* description() { return "boom!"; }
};

template <typename TCode, TCode SuccessVal>
void panic(Error<TCode, SuccessVal> e);

//! So assume you have Error<ErrorCode, SuccessVal>
//! Place this in the header where the panic function prototype is declared.
//! You'll need one for each type or Error<T,Val> instantiation.
extern template void panic<int, 0>(Error<int, 0> e);
extern template void panic<int, 1>(Error<int, 1> e);
extern template void panic<int, 2>(Error<int, 2> e);

#endif//PANIC_HPP

      

panic.ipp

#ifndef PANIC_IPP
#define PANIC_IPP

#include "panic.hpp"
#include <cstdio>
#include <cstdlib>

template <typename TCode, TCode SuccessVal>
inline void panic(Error<TCode, SuccessVal> e)
{
    printf("Unrecoverable error: %s scode: %d\n", e.description(), SuccessVal);
    std::abort();
}

#endif//PANIC_IPP

      



panic.cpp

#include "panic.ipp"

//! Declare the instantiation for each Error<T, Val> which you don't want duplicated.
template void panic<int, 0>(Error<int, 0> e);
template void panic<int, 1>(Error<int, 1> e);
template void panic<int, 2>(Error<int, 2> e);

      

main.cpp

#include "panic.hpp"

int main()
{
    panic(Error<int, 0>());
    panic(Error<int, 1>());//! These compile/link but aren't called due to abort.
    panic(Error<int, 2>());//! ...
    //panic(Error<int, 3>()); //! won't link due to unresolved symbol as not instantiated.
    return 0;
}

      

+1


source







All Articles