【问题标题】:C++: Can I enforce at least 1 agument in a template parameter packC++:我可以在模板参数包中强制执行至少 1 个参数吗
【发布时间】:2018-07-29 23:56:39
【问题描述】:

这更多是我正在考虑的设计约束。如果您想知道语法,则此代码的 sn-p 是针对虚幻引擎的 - 但这应该与问题无关。

此模板代码仅采用用户想要实例化的对象类型 - 然后创建一个拥有容器对象并调用所有者的 add 方法。 (实际构建逻辑由业主负责)

template<typename T>
void UEntityFactory::Create_Impl(AEntityObject* owner)
{
    static_assert( TPointerIsConvertibleFromTo<T, UEntity>::Value, "[UEntityFactory] ERROR: Attempting to create a non-entity type");
    owner->AddEntity<T>();
}

// Templated create accepting variadic arguments; it first creates the housing entity object
// and then recursively generates code for each argument.
template <typename T, typename...Args>
AEntityObject* UEntityFactory::Create()
{
    // Create the owning EntityObject
    AEntityObject* obj = NewObject<AEntityObject>(this);
    Create_Impl<T>(obj);

    // Recursively call Create_Impl for Args
    Create_Impl<Args...>(obj);
    return obj;
}

根据我从模板参数包中了解到的情况,可变参数(在我的例子中是 ...Args)是可选的。拥有两个类型名而不是一个参数包的基本原理是确保用户至少传递一个模板参数。

我想限制创建过程以确保始终创建有效对象。但是如果我调用如下行,上面的代码不会编译:

AEntityObject* obj = factory.Create<EntityA>();

我认为我设置模板声明的方式需要另一个参数。 错误如下:

  • 'UEntityFactory::Create_Impl': 找不到匹配的重载函数
  • 注意:请参阅正在编译的函数模板实例化“AEntityObject *UEntityFactory::Create&lt;UDebugEntityA,&gt;(void)”的参考
  • 'void UEntityFactory::Create_Impl(AEntityObject *)': 无法推断出'T'的模板参数

这是其中一个错误,我有点知道它为什么会发生,但我无法清楚地表达它。果然,如果我将 Create 函数的声明更改为以下,它编译得很好:

template <typename...Args>
AEntityObject* UEntityFactory::Create()
{
    AEntityObject* obj = NewObject<AEntityObject>(this);

    // Recursively call Create_Impl for Args
    Create_Impl<Args...>(obj);
    return obj;
}

然而这意味着,用户现在可以写

AEntityObject* obj = factory.Create<>();

这是我想要避免的。有没有一种优雅的方法来强制至少一个模板参数,以及一个可选的 set variadic 参数?我不能重载 Create 方法,因为这会产生不明确的调用。

编辑:我搞砸了,我错误地认为我的参数一开始就正确展开 - 感谢 Jarod42 的纠正。

【问题讨论】:

  • template &lt;typename Arg, typename ... Args&gt; 怎么样?

标签: c++ variadic-templates template-meta-programming


【解决方案1】:

好吧,您可以添加一个static_assert() 来进行检查并发出更有意义的编译时错误:

template <typename...Args>
AEntityObject* UEntityFactory::Create()
{
    static_assert(sizeof...(Args) > 0, "Create() would like to have at least one object type to create...");

    AEntityObject* obj = NewObject<AEntityObject>(this);

    // Recursively call Create_Impl for Args
    Create_Impl<Args...>(obj);
    return obj;
}

【讨论】:

    【解决方案2】:
    template <typename T, typename...Args>
    AEntityObject* UEntityFactory::Create()
    {
        // [..]
    
        // Recursively call Create_Impl for Args
        Create_Impl<Args...>(obj);
    }
    

    不是递归调用

    事实上,您似乎很难迭代您的可变参数模板。

    然后你可以使用:

    template <typename T, typename...Args>
    AEntityObject* UEntityFactory::Create()
    {
        // Create the owning EntityObject
        AEntityObject* obj = NewObject<AEntityObject>(this);
        Create_Impl<T>(obj);
    
        // C++17: folding expression
        (Create_Impl<Args>(obj), ...);
    
        return obj;
    }
    

    之前:

    template <typename T, typename...Args>
    AEntityObject* UEntityFactory::Create()
    {
        // Create the owning EntityObject
        AEntityObject* obj = NewObject<AEntityObject>(this);
        Create_Impl<T>(obj);
    
        const int dummy[] = { 0, (Create_Impl<Args>(obj), 0)...};
        static_cast<void>(dummy); // Avoid warning for unused variable
    
        return obj;
    }
    

    【讨论】:

    • 这实际上解决了我遇到的两个问题 - 感谢您的更正。知道我是否会在其他展开场景中遇到此列表初始化程序语法会有所帮助 - 这是展开参数包的最佳方式吗?因为它看起来确实不寻常......
    • C++17 的方式很干净,是 IMO 的必经之路。对于 C++11/C++14,有几种变体可以迭代“元组”。递归也是一种可能性(C++17 甚至有if constexpr 以避免额外的过载)。
    猜你喜欢
    • 1970-01-01
    • 2016-12-09
    • 2018-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-16
    • 2015-08-01
    相关资源
    最近更新 更多