65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 17 变长模板包到运行时

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (8投票s)

2018年1月6日

CPOL

2分钟阅读

viewsIcon

21408

通过变长模板减少函数递归

引言

在我的上一篇 C++ 17 文章 这里 中,我不太喜欢 std::any。 然而,我想到一个有趣的技巧,可以减少可变模板函数中所需的递归。

C++ 11 和 C++ 17

首先,旧的方法。来自 Cake Processor 的一篇 优秀文章,关于可变参数 printf,我们有以下代码:

 

void safe_printf(const char *s)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                throw std::runtime_error("invalid format string: missing arguments");
            }
        }
        std::cout << *s++;
    }
}

template<typename T, typename... Args>
void safe_printf(const char *s, T value, Args... args)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                std::cout << value;
                safe_printf(s + 1, args...); // call even when *s == 0 to detect extra arguments
                return;
            }
        }
        std::cout << *s++;
    }
    throw std::logic_error("extra arguments provided to printf");
}

 

两个函数。一个可变模板函数,另一个是最后的 const char* 函数,当参数包中的所有参数都展开后,它会被调用。 最重要的困难是必须在所有此类模板中进行的编译时递归。

但是,请记住参数包使用逗号分隔符展开参数

template <typename ... Args> 
void foo(Args ... args)
{
    
}

foo(1,2,3,"hello"); // means that args is unpacked as such.

 

 

为什么不使用 initializer_list 将这些值存储在 vector 中,以便在运行时使用 for 循环访问它们呢?

template <typename ... Args>
void foo(Args ... args)
{
    std::vector<std::any> a = {args ...};
}

非常酷。 事实是这些项存储在 std::any 中,意味着任何东西都可以传递给它,现在可以在运行时访问它。 请注意,这可行是因为 std::any 本身不是一个模板;它将包含的对象存储为通用指针,并通过 typeinfo 测试它是否与传递给它的类型匹配。 顺便说一句,我认为类型测试应该放宽,例如,如果你有一个包含 int 的 std::any,为什么不能使用 any_cast<long>() 返回它呢?

现在新的 printf 在一个函数中(好的,为了效率,人们会循环遍历 vector,但现在这并不重要)

template<typename ... many>
void safe_printf2(const char *s, many ... args)
{
    vector<any> a = {args ...};

    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {

                if (a.empty())
                    throw std::logic_error("Fewer arguments provided to printf");

                if (a[0].type() == typeid(string)) cout << any_cast<string>(a[0]);
                if (a[0].type() == typeid(int)) cout << any_cast<int>(a[0]);
                if (a[0].type() == typeid(double)) cout << any_cast<double>(a[0]);

                a.erase(a.begin());
                s++;
            }
        }
        std::cout << *s++;
    }
}

 

// ----
int main()
{
 safe_printf2("Hello % how are you today? I have % eggs and your height is %","Jack"s, 32,5.7);
 // Hello Jack how are you today? I have 32 eggs and your height is 5.7
}

通常,使用 vector<any> 允许你将参数包扩展到运行时,并使用 for 循环对其进行操作。

当然,initializer_list 会复制所有参数。 所以你可能想使用指针

vector<any> a = {&args...};

if (a[0].type() == typeid(string*)) cout << *any_cast<string*>(a[0]);

或者,引用,借助 std::ref 和 std::reference_wrapper(我更喜欢指针。 此外,std::reference_wrapper 编写起来更长,理解起来更困难,而且它内部也使用指针,因为它不能使用其他任何东西。可耻啊)

vector<any> a = {std::ref(args)...};

if (a[0].type() == typeid(std::reference_wrapper<string>))  cout << any_cast<std::reference_wrapper<string>>(a[0]).get();

 

历史

2018年1月6日:首次发布

© . All rights reserved.