首页 > 解决方案 > 模板模板参数推演指南

问题描述

我有一组这样的结构类:

template<typename T>
struct Foo {
    T x_;
    T y_;
    constexpr Foo(T x, T y) : x_{x}, y_{y} {}
};

template<typename T, typename U, template<U> class Func>
class Bar {
private:
    Foo<T> foo_;
    Func<U> func_
    size_t n_;
public:
    Bar(Foo<T> foo, size_t n, Func<U> func) :
      foo_{foo},
      n_{n},
      func_{func}
    {}
};

我正在尝试为这个类模板创建一个演绎指南......

// Doesn't compile
template<typename T, typename U, template<U> class Func>
Bar(Foo<T>, U, Func<U>)->
Bar<T,U,Func>;

// Doesn't compile
template<typename T, typename U, template<U> class Func>
Bar(Foo<T>, U, Func)->
Bar<T,U,Func>;

当模板参数恰好是模板本身时,我不确定此的正确语法,其中模板化参数将是类将存储的函数指针、函数对象、仿函数或 lambda。

当我尝试在其中使用U不允许Func<>状态并且如果我将其删除为没有任何模板参数时,它会指出“模板模板参数'Func'的参数列表丢失” ......type nameFunc

我的预期用途Bar如下所示:

template<typename T>
constexpr T funcA(T x) {
    return x;
}

template<typename T>
constexpr T funcB(T x) {
    return x*x;
}

int main() {
    Bar bar1{Foo{1.0, 3.0}, 1000, funcA<double>}; 
    Bar bar2{Foo{3.7, 4.0}, 500, funcB<float>};

    return 0;
}  


编辑- 本部分适用于用户:piotr-skotnicki

注意:上面是一个伪代码,与我的类的表示具有相同的签名......现在我可以再次访问我的 IDE,这里是“真实”源。

积分器.h

#pragma once

//#include <type_traits>

template <typename Field>
struct Limits {
    Field lower;
    Field upper;

    constexpr Limits(Field a = 0, Field b = 0) : 
        lower{ a < b ? a : b }, 
        upper{ a < b ? b : a }
    {}
};    

template <typename LimitType, typename Func>
class Integrator {       
    //static_assert(std::is_invocable_v<Func&>, "Invalid callable");
private:
    Limits<LimitType> limits_;
    size_t step_size_;
    Func integrand_;

public:
    Integrator(Limits<LimitType> limits, size_t stepSize, Func integrand) :
        limits_{ limits },
        step_size_{ stepSize },
        integrand_{ integrand }
    {}

    constexpr auto evaluate() {
        auto distance = limits_.upper - limits_.lower;     
        auto dx = distance / step_size_;       
        return calculate(dx);
    }        

private:
    template<typename ValueType>
    constexpr auto calculate(ValueType dx) {
        ValueType result = 0.0;
        for (size_t i = 0; i < step_size_; ++i) {
            auto dy = integrand_(limits_.lower + i * dx);
            auto area = dy * dx;
            result += area;
        }
        return result;
    }

};

//template <typename LimitType, typename Func>
//Integrator(Limits<LimitType>, size_t, Func)
//->Integrator<LimitType, Func>;

主文件

#include <iostream>
#include <exception>

#include "Integrator.h"


double funcE(double x) {
    return x;
}

template <typename T>
constexpr T funcA_t(T x) {
    return x;
}    

// This Works! 
int main() {
    try {
        std::cout << "Integration of f(x) = x from a=3.0 to b=5.0\nwith an expected output of 8\n";
        Integrator integratorA{ Limits{3.0, 5.0}, 10000, funcA };
        std::cout << integratorA.evaluate() << '\n';    
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

// This was failing to compile... but now seems to work for some reason...
int main() {
    try {
        std::cout << "Integration of f(x) = x from a=3.0 to b=5.0\nwith an expected output of 8\n";
        Integrator integratorA{ Limits{3.0, 5.0}, 10000, funcA_t<double> };
        std::cout << integratorA.evaluate() << '\n';    
    }
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

// Same as above...
Integrator integrator{ Limits{3.0, 5.0}, 10000, &funcA_t<double> };
// wasn't compiling...

事先 Visual Studio 抱怨它无法推断模板参数Func......我不知道为什么......

我不知道发生了什么……也许 Visual Studio 出了问题……它现在似乎正在工作……很奇怪……

标签: c++templatesparametersc++17deduction-guide

解决方案


首先,下面的语法:

template <typename T, typename U, template <U> class Func>

并不意味着Func它将有一个单一类型的模板参数,与实例本身的第二个模板参数U相同Bar

这意味着这Func是一个类模板,它采用 type 的非类型模板参数U。如果Func需要类型模板参数,则应变为:

template <typename T, typename U, template <typename> class Func>
//                                          ~~~~~~~^

和一个匹配的扣除指南:

template <typename T, typename U, template <typename> class Func>
Bar(Foo<T>, U, Func<U>) -> Bar<T, U, Func>;

然而,Func它仍然是一个模板模板参数,并且只接受别名/类/结构模板,并且永远不会匹配函数指针类型,也不会匹配 lambda 表达式。如果您打算在Bar实例中存储任何可调用对象,则使用任何类型作为模板参数,并让演绎指南推断它是什么:

template <typename T, typename U, typename Func>
//                                ~~~~~~~^

为了确保它可以使用 type 的(左值)参数调用U,只需放置一个约束,如 a static_assert

#include <type_traits>

template <typename T, typename U, typename Func>
class Bar {
    static_assert(std::is_invocable_v<Func&, U&>, "Invalid callable");
private:
    Foo<T> foo_;
    Func func_;
    U n_;
public:
    Bar(Foo<T> foo, U n, Func func) :
      foo_{foo},
      func_{func},
      n_{n}
    {}
};

演示

另请注意,您不需要显式推导指南,因为它会从构造函数中隐式生成。


但是,如果您事先不知道U将使用什么作为 的参数Func,则不应将其视为构造函数定义中的问题,也不应将其视为类定义本身的问题。这清楚地表明该参数将从某个外部来源提供,并且在某个地方您会知道并且能够验证它是否适合可调用对象。

当然,您不应该尝试推断可调用对象的确切签名。它在实践中没有用,很可能意味着您的设计存在缺陷。

也就是说,一旦你最终知道使用什么类型的参数,就放一个static_assert,例如:

template <typename ValueType>
constexpr auto calculate(ValueType dx) {
    static_assert(std::is_invocable_v<Func&, ValueType&>, "Invalid value type");
    ValueType result = 0.0;
    // ...
    return result;
}

或者,您可以使用或使calculateSFINAE 友好:std::enable_if_trequires

template <typename ValueType>
constexpr auto calculate(ValueType dx)
    -> std::enable_if_t<std::is_invocable_v<Func&, ValueType&>, ValueType> {
    ValueType result = 0.0;
    // ...
    return result;
}

推荐阅读