【问题标题】:partial class template argument deduction in C++C ++中的部分类模板参数推导
【发布时间】:2022-01-12 12:19:04
【问题描述】:

我有一个模板类,但只能从构造函数中推导出部分模板参数。

有没有办法在调用构造函数时在尖括号内提供其余的模板参数?

假设我们使用的是 C++17。

template<typename T1, typename T2>
struct S
{
    T2 t2;

    S(const T2& _t2) : t2{_t2} {}

    void operator()(const T1& t1)
    {
        std::cout << t1 << ", " << t2 << '\n';
    }
};

int main()
{
    S<int, double> s {3.14};

    std::function<void(int)> func = s;
    
    func(42);

    // What I want:
    //S<int> s2 {3.14};     <- T1 is provided in the angle brackets, T2 is deduced
    //std::function<void(int)> func2 = s;
    //func2(42);
}

据我所知,我们需要在尖括号中提供所有模板参数,或者不提供任何参数并使用 CTAD。 问题是我不想编写所有模板参数(在我的实际用例中,它们中有 5-6 个,它们非常冗长)但我也不想在构造函数中传递所有参数,因为其中一些不用于构造对象。对于 operator() 方法,我只需要它们的类型。

我无法将 operator() 方法模板化,因为我想将它绑定到 std::function 对象,并且我无法在绑定期间推断模板参数类型。所以这就是为什么我需要包装类中的所有类型。

这种部分模板推导存在于函数中。

例如:

template<typename T1, typename T2>
void foo(const T2& t2)
{
    T1 t1{};
    std::cout << t1 << ", " << t2 << '\n';
}

int main()
{
    foo<int>(3.4); //T1 is explicitly int, T2 is deduced to be double
}

我目前的解决方案是利用这个特性,通过函数构造对象:

template<typename U1, typename U2>
S<U1, U2> construct_S(const U2& t2)
{
    return S<U1, U2>{t2};
}

int main()
{
    auto s2 = construct_S<int>(1.5);
    std::function<void(int)> func2 = s2;
    func2(23);
}

我觉得这个解决方案很笨拙,因为我们使用外部函数来构造对象。

我想知道是否有更清洁的解决方案。

也许有一些扣除指南?我不确定。

【问题讨论】:

标签: c++ templates c++17 ctad


【解决方案1】:

正如评论中提到的,您可以使用嵌套类,以便可以单独提供两个参数(一个显式推导出另一个):

template<typename T1>
struct S {
    template <typename T2>
    struct impl {
        T2 t2;    
        impl(const T2& _t2) : t2{_t2} {}
    };

    template <typename T2>
    impl(const T2&) -> impl<T2>;
};


int main() {
    S<int>::impl<double> s {3.14};
    S<int>::impl s2 {3.14};    // <- T1 is provided in the angle brackets, T2 is deduced
}

我发现了这个How to provide deduction guide for nested template class?。不过,上面的代码在 gcc 和 clang 上都没有问题:https://godbolt.org/z/MMaPYGbe1

如果重构类模板不是一种选择,辅助函数是一种常见且干净的解决方案。标准库有许多 make_xxx 函数,其中一些仅在 CTAD 出现之前才需要。

【讨论】:

    【解决方案2】:

    最简单的方法是提供工厂函数来接管类型的推导:

    template<typename T1, typename T2>
    auto makeS(T2 x) -> S<T1, T2>
    {
        return S<T1, T2>{x};
    }
    

    https://godbolt.org/z/4cPTdv7e3

    【讨论】:

      【解决方案3】:
      template<typename T2>
      struct S
      {
          T2 t2;
      
          S(const T2& _t2) : t2{_t2} {}
          template<typename T1>
          void operator()(const T1& t1)
          {
              std::cout << t1 << ", " << t2 << '\n';
          }
      };
      
      int main()
      {
          S s {3.14}; // T1 not provided
          std::function<void(int)> func2 = s; // T1 deduced by std function
          func2(42); // works
      }
      

      我刚刚删除了T1,并将其设为operator() 的模板参数,一切正常。

      Live example.

      【讨论】:

      • 不适用于我的情况。抱歉,我的代码示例中没有显示它,但想象一下,如果 operator() 定义是这样的:void operator()() { T1 t1{}; std::cout &lt;&lt; t1 &lt;&lt; ", " &lt;&lt; t2 &lt;&lt; '\n'; } 你无法推断 T1 的类型,因为它不是方法参数的一部分。
      猜你喜欢
      • 2019-12-25
      • 1970-01-01
      • 2020-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-16
      • 1970-01-01
      • 2011-10-07
      相关资源
      最近更新 更多