可变参数模板 class 隐式转换

Variadic template class implicitly converting

我有一个 class,需要验证它的函数调用是否使用正确的参数调用。函数签名始终相同(无 1 个参数类型)。所以,我自然而然地采用了模板化方法。所以通常验证策略会针对它可以处理的每种数据类型有一个模板参数:

using P = Policy<int, double, UserDefined>

或者类似的东西。 我得到它来编译,但需要注意的是,如果 doubleint(或任何 double 实际上可以转换为)都是模板参数,则 double 将是隐式转换。

政策如下所示:

template <typename... T>
class BasicValidationPolicy { };

template <>
class BasicValidationPolicy<>
{
public:
    void RegisterSetHandler();
};

template <typename T, typename... Rest>
class BasicValidationPolicy<T, Rest...> : public BasicValidationPolicy<Rest...>
{
public:
    using SetHandler = std::function<void(int, T)>;
    
    void RegisterSetHandler(const SetHandler& handler)
    {
        m_setHandler = handler;
    }
    
    void Set(int n, const T& val) {
        if (m_setHandler) {
            m_setHandler(n, val);
        }
    }
    
private:
    SetHandler m_setHandler{nullptr};
};

使用它的class...

template <typename ValidatorPolicy>
class MyClass : public ValidatorPolicy  {
public:

    void OnSetInt(int n, int64_t v)
    {
        ValidatorPolicy::Set(n, v);
    }
    
    void OnSetDouble(int n, double d)
    {
        ValidatorPolicy::Set(n, d);
    }
};

用法:

int main()
{
    using Policy = BasicValidationPolicy<int64_t, double>; // doesn't work
    MyClass<Policy> m;
    
    m.Policy::RegisterSetHandler([](int i, double value) {
        // by this point value is an int64_t
        std::cout << "Got double " << i << ", " << value << "\n";
    });
   
    double d{35.2135}; 
    m.OnSetDouble(1, d);
}

启动,这样做有效

using Policy = BasicValidationPolicy<double, int64_t>;

所以我想我遗漏了一些有关模板推导的内容。看起来它试图匹配 doublestd::int64_t 说“嗯,足够好”,然后继续。很高兴知道(某种程度上)绕过它的方法,但看起来维护起来非常棘手。

这很复杂...

首先:您有一个递归模板 class,BasicValidationPolicy,您在其中定义了两个方法,并且您想要所有方法,用于 class 的所有递归步骤,可用。

不幸的是,派生 classes 中的方法定义隐藏了基础 classes 中的方法。

要取消隐藏继承的方法,您必须显式添加一对 using

using BasicValidationPolicy<Rest...>::Set;
using BasicValidationPolicy<Rest...>::RegisterSetHandler;

此时,代码无法编译,因为在基本情况 class 中您需要一个 Set() 和一个 RegisterSetHandler()。您已经声明了一个虚拟 RegisterSetHandler() 但不是一个虚拟 Set()。你必须加一个,所以地面情况变成

template <>
class BasicValidationPolicy<>
{
public:
    void RegisterSetHandler();
    void Set();
};

现在您的 MyClass<Policy> 对象公开两个 RegisterSetHandler() 方法(之前只有一个):一个接收 std::function<void(int, std::int64_t)>,另一个(隐藏之前)接收 std::function<void(int, double)>

但是,当您传递 lambda 时,就会遇到先有鸡还是先有蛋的问题:lambda 可以转换为 std::function 但不是 std::function。所以不能用来推导 std::function 的模板参数,因为在推导它们之前要知道类型。

一个可能的解决方案是在调用中强加一个 lambda/std::function 转换

// ..........................VVVVVVVVVVVVVV
m.Policy::RegisterSetHandler(std::function{[](int i, double value) {
                             // by this point value is an int64_t
                             std::cout << "Got double " << i << ", " << value << "\n";
                             }});
// ...........................^

还使用 C++17 中引入的模板推导指南。

所以你的代码变成了

#include <iostream>
#include <functional>

template <typename... T>
class BasicValidationPolicy { };

template <>
class BasicValidationPolicy<>
{
public:
    void RegisterSetHandler();
    void Set();
};

template <typename T, typename... Rest>
class BasicValidationPolicy<T, Rest...> : public BasicValidationPolicy<Rest...>
{
public:
    using SetHandler = std::function<void(int, T)>;

    using BasicValidationPolicy<Rest...>::Set;
    using BasicValidationPolicy<Rest...>::RegisterSetHandler;
    
    void RegisterSetHandler(const SetHandler& handler)
    {
        m_setHandler = handler;
    }
    
    void Set(int n, const T& val) {
        if (m_setHandler) {
            m_setHandler(n, val);
        }
    }
    
private:
    SetHandler m_setHandler{nullptr};
};

template <typename ValidatorPolicy>
class MyClass : public ValidatorPolicy  {
public:

    void OnSetInt(int n, int64_t v)
    {
        ValidatorPolicy::Set(n, v);
    }
    
    void OnSetDouble(int n, double d)
    {
        ValidatorPolicy::Set(n, d);
    }
};


int main ()
 {
   using Policy = BasicValidationPolicy<int64_t, double>; // doesn't work
   MyClass<Policy> m;
    
   m.Policy::RegisterSetHandler(std::function{[](int i, double value) {
                                std::cout << "Got double " << i << ", " << value << "\n";
                                }});
   
    double d{35.2135}; 
    m.OnSetDouble(1, d);
}

递归定义有一个小的替代方案,可能更容易使用...

template<typename T>
class ValidationPolicy {
  // Set/Register/etc
};

template <typename... Ts>
class BasicValidationPolicy : public ValidationPolicy<Ts>... {
 public:
  using ValidationPolicy<Ts>::Set...;
  using ValidationPolicy<Ts>::RegisterSetHandler...;
};

这可能会对编译时间和开发的其他方面产生一些影响,但很可能相对较小。例如,如果您的应用程序中有数十个 类 用于数百个不同的策略组合,则递归定义将导致更多不同的类型和更大的二进制文件来支持它。例如,在递归定义中使用 BasicValidationPolicy<T1, T2, T3>BasicValidationPolicy<T3, T2, T1> 将在层次结构中生成 7 个不同的类型(空的类型在两个扩展中共享)。在更扁平的层次结构中,同样的事情将是 5 种不同的类型——一种用于 T1, T2, T3 中的每一种,另一种用于每种组合。添加 BasicValidationPolicy<T2, T3, T1> 会递归地添加 3 种类型,但会在平面形式中增加 1 种类型。

@max66 的回答并没有错,只是需要考虑其他问题。