【问题标题】:The variadic template constructor of my class cannot modify my class members, why is that so?我的类的可变参数模板构造函数不能修改我的类成员,为什么会这样?
【发布时间】:2019-09-04 01:19:07
【问题描述】:

我一直在处理我们教授的任务,我必须在其中解决可变参数模板类。 问题是,我无法修改递归构造函数中的类成员。我不知道为什么会这样,一旦它进入下一个构造函数调用,它就会丢弃我对变量的更改。

我尝试过的:

  • 使用指针int* count 而不是int count

  • 使用setter设置counter

我已经在谷歌上搜索了几个小时,但找不到解决问题的答案。

头文件“test.h”:

#include <cstdarg>
#include <iostream>

class Counter {
private:
    int count = 0;
    int tmp;

public:
    template <typename... Rest> Counter (int t, Rest... rest) {
        count++;
        std::cout << "start recursive number " << count << "...\n";
        Counter(rest ...);
        tmp = t;
        std::cout << "end recursive number " << count << "...\n";
    }
    Counter (int t) {
        count++;
        tmp = t;
        std::cout << "reached end of recursive ->  " << count << "\n";
    }
};

main.cpp:

#include "test.h"
int main () {
    Counter a {0, 1, 2, 3, 4};
}

我得到的输出:

start recursive number 1...
start recursive number 1...
start recursive number 1...
start recursive number 1...
reached end of recursive ->  1
end recursive number 1...
end recursive number 1...
end recursive number 1...
end recursive number 1...

【问题讨论】:

  • 也许这里的意图是让count 成为static,我不确定这是否考虑修改变量?无论哪种方式,我想您的意图是打印tmp

标签: c++ c++11 recursion variadic-templates


【解决方案1】:

Counter(rest ...); 创建一个未命名的临时对象,它不会递归调用该对象的构造函数。每个对象都有自己的count 生成,因此您会得到 1 1 1 1

的流

如果你想将对象初始化委托给不同的构造函数,那么它应该出现在成员初始化列表中。不过,这似乎不是一个好主意:

template <typename... Rest> Counter (int t, Rest... rest)
:   Counter{rest...}
{
    count++;
    std::cout << "start recursive number " << count << "...\n";
    tmp = t;
    std::cout << "end recursive number " << count << "...\n";
}

【讨论】:

    【解决方案2】:

    正如 VTT 所解释的,在构造函数体内调用 Counter() 会创建一个新的 Counter() 对象。

    您可以递归调用构造函数,但您必须在初始化列表中执行此操作:查找“委托构造函数”以获取更多信息。

    我还建议您不要在构造函数体内初始化(和修改)成员对象。

    如果您的目标是用参数的数量初始化count,用最后一个参数的值初始化tmp,我提出以下(基于“标签调度”)的解决方案

    class Counter
     {
       private:
          struct tag
           { };
    
          int count = 0;
          int tmp;
    
          Counter (tag tg, std::size_t c0, int t) : count(c0), tmp{t}
           { std::cout << "end: " << tmp << ", " <<count << "\n"; }
    
          template <typename... Rest>
          Counter (tag t0, std::size_t c0, int t, Rest... rest) 
             : Counter{t0, c0, rest...} 
           { std::cout << "recursion: " << tmp << ", " << count << "\n"; }
    
       public:
          template <typename... Rest>
          Counter (Rest... rest) : Counter{tag{}, sizeof...(Rest), rest...} 
           { std::cout << "start: " << tmp << ", " << count << "\n"; }
     };
    

    您还可以避免标签分派和构造函数递归,将关于 rest... 的递归委托给用于初始化 tmp 的方法(可能是 staticconstexpr

    class Counter
     {
       private:
          int count = 0;
          int tmp;
    
          static int getLastInt (int i)
           { return i; }
    
          template <typename ... Rest>
          static int getLastInt (int, Rest ... rs)
           { return getLastInt(rs...); }
    
       public:
          template <typename... Rest>
          Counter (Rest... rest)
             : count(sizeof...(Rest)), tmp{getLastInt(rest...)} 
           { std::cout << tmp << ", " << count << "\n"; }
     };
    

    题外话:确切地说,您的Counter 类不是“可变参数模板类”。

    这是一个普通的(不是模板)类,有一个(在我的第一个解决方案中是两个)可变参数模板构造函数。

    -- 编辑--

    OP 询问

    如果我需要在编译时间内获取计数作为静态 const 变量和具有计数器长度的 int 数组并作为类成员怎么办? (数组将填充所有构造函数参数)这在 C++ 的可能性范围内吗?

    只有当计数器是类的所有实例之间的公共值时,静态常量(也可能是 constexpr)才有意义。

    目前没有意义,因为您的 Counter 接受不同长度的初始化列表。

    但是假设构造函数的参数编号是一个模板参数(比如N)......在这种情况下,count 只是N 并且可以是static constexpr。您可以为这些值定义一个std::array&lt;int, N&gt;(也是一个int[N],但我建议尽可能避免使用C 样式的数组,而是使用std::array),并且,使构造函数constexpr,你可以强加编译时初始化。

    以下是一个完整的 C++14 编译示例(使用 std::make_index_sequencestd::index_sequence,不幸的是,它们只能从 C++14 开始使用)。

    请注意,我已将main() 中的f8 变量定义为constexpr:只有这样您才能强加(假设没有原样规则)f8 在编译时被初始化

    #include <array>
    #include <iostream>
    #include <type_traits>
    
    template <typename T, std::size_t>
    using getType = T;
    
    template <std::size_t N, typename = std::make_index_sequence<N>>
    struct foo;
    
    template <std::size_t N, std::size_t ... Is>
    struct foo<N, std::index_sequence<Is...>>
     {
       static_assert( sizeof...(Is), "!" );
    
       static constexpr auto count = N;
    
       const std::array<int, N> arr;
    
       constexpr foo (getType<int, Is> ... is) : arr {{ is ... }}
        { }
     };
    
    int main ()
     {
       constexpr foo<8u>  f8 { 2, 3, 5, 7, 11, 13, 17, 19 };
    
       for ( auto const & i : f8.arr )
          std::cout << i << ' ';
    
       std::cout << std::endl;
     }
    

    如果你可以使用支持C++17的编译器,你也可以使用foo的推导指南

    template <typename ... Args>
    foo(Args...) -> foo<sizeof...(Args)>;
    

    所以不需要解释定义f8的模板参数

    // .......VVV  no more "<8u>"
    constexpr foo  f3{ 2, 3, 5, 7, 11, 13, 17, 19 };
    

    因为它是从构造函数的参数个数推导出来的。

    【讨论】:

    • 非常感谢您的提示,帮助很大!如果我需要在编译时间内将计数作为静态 const 变量和具有计数器长度的 int 数组并作为类成员,该怎么办? (数组将填充所有构造函数参数)这在 C++ 的可能性范围内吗?
    • @PrettyCoffee - 让我想想...考虑一下 static 变量是类的所有实例共有的变量,所以 static count 变量(常量,此外) 仅当count 值对Counter 的所有实例都通用时才有意义。目前没有意义,因为您可以使用不同数量的参数调用不同实例的构造函数。如果模板参数的编号是类的模板参数,则不同。给我几分钟,我准备一个例子来说明我的意思。
    • @PrettyCoffee - 答案改进;希望这会有所帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-18
    • 2021-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-06
    相关资源
    最近更新 更多