避免多个不依赖于模板类型的函数的模板瞬时的优雅方法

Elegant way to avoid multiple template instantiatons of function that doesn't depend on templated type

本文关键字:方法 函数 依赖于 类型      更新时间:2023-10-16

我有一个基本的类,看起来像这样:

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;
};

它所做的只是封装某种错误代码枚举类,以便为日志记录提供更多的上下文。

现在假设我有一个函数panic:

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

使用-fdump-tree-original编译显示,在我的例子中,这将产生两个完全相同代码的不同函数。这是您所期望的,但可能不是您所希望的。

一个明显的路径是一个基类,它只有消息和一个接受消息的构造函数,但我发现这相当不吸引人。

我们从不使用错误代码本身,所以我们所做的一切都不依赖于t。我如何避免大量的模板实例化编译成基本相同的代码?

另一个理想的特性是确保无论TCode是什么类型,它都可以强制转换为整数类型。

这段代码的一个明显的分解是:

[[noreturn]] void panic_with_message(char const * msg)
{
    std::printf("Panic!nUnrecoverable error: %sn", 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());
}

可以只将模板和函数声明放在头文件中,并将函数定义放在单独的翻译单元中。这将使代码膨胀降到最低:

// 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

另一个选择是使用外部模板声明。c++11编译器和更早的visual studio版本支持此功能(作为语言扩展)。假设您有相当少的Error实例化,您可以为每个实例化定义一个外部的panic模板声明,并将实际的函数实例化放在单个翻译单元中。这可以确保每个panic变体只有一个实例化。

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: %dn", 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;
}