模板模板参数推导指南
Deduction Guide for a template template parameter
我有一组结构的 class 是这样的:
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}
{}
};
我正在尝试为这个 class 模板创建演绎指南...
// 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>;
当模板参数恰好是模板本身时,我不确定正确的语法,其中模板化参数将是函数指针、函数对象、仿函数或 class 将存储。
当我尝试在 Func<>
中使用 U
时,它指出 "type name
是不允许的" 如果我删除它是只是 Func
没有任何模板参数,它指出,"argument list for template template parameter 'Func' is missing"...
我对 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
注意:上面是一个伪代码,其签名与我的 classes 的表示相同...现在我可以再次访问我的 IDE,这里是 "real"来源。
Integrator.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>;
main.cpp
#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 有问题...它现在似乎在工作...很奇怪...
首先,语法如下:
template <typename T, typename U, template <U> class Func>
并不意味着 Func
将具有单个 type 模板参数,与 [=19] 的第二个模板参数 U
相同=]实例本身。
这意味着 Func
是一个 class 模板,它采用 非类型 类型 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
仍然是一个 template 模板参数,并且只接受 alias/class/struct 模板,并且永远不会匹配函数指针类型,也不会匹配拉姆达表达式。如果您打算在 Bar
个实例中存储任何可调用对象,则使用任何类型作为模板参数,并让推导指南推断出它是什么:
template <typename T, typename U, typename Func>
// ~~~~~~~^
为了确保它 将 可以用 U
类型的(左值)参数调用,只需放置一个 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
的参数,那么这不应被视为构造函数定义中的问题,也不在 class 定义本身。这清楚地表明该参数将从某个外部源提供,并且在某个地方您将知道并且将能够验证它是否是否适合可调用。
当然,您不应该试图推断出可调用对象的确切签名。它在实践中毫无用处,很可能意味着您的设计存在缺陷。
也就是说,一旦您最终知道使用了哪种类型的参数,请在此处输入 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;
}
或者,您可以使用 std::enable_if_t
或 requires
:
使 calculate
SFINAE 友好
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;
}
我有一组结构的 class 是这样的:
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}
{}
};
我正在尝试为这个 class 模板创建演绎指南...
// 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>;
当模板参数恰好是模板本身时,我不确定正确的语法,其中模板化参数将是函数指针、函数对象、仿函数或 class 将存储。
当我尝试在 Func<>
中使用 U
时,它指出 "type name
是不允许的" 如果我删除它是只是 Func
没有任何模板参数,它指出,"argument list for template template parameter 'Func' is missing"...
我对 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
注意:上面是一个伪代码,其签名与我的 classes 的表示相同...现在我可以再次访问我的 IDE,这里是 "real"来源。
Integrator.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>;
main.cpp
#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 有问题...它现在似乎在工作...很奇怪...
首先,语法如下:
template <typename T, typename U, template <U> class Func>
并不意味着 Func
将具有单个 type 模板参数,与 [=19] 的第二个模板参数 U
相同=]实例本身。
这意味着 Func
是一个 class 模板,它采用 非类型 类型 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
仍然是一个 template 模板参数,并且只接受 alias/class/struct 模板,并且永远不会匹配函数指针类型,也不会匹配拉姆达表达式。如果您打算在 Bar
个实例中存储任何可调用对象,则使用任何类型作为模板参数,并让推导指南推断出它是什么:
template <typename T, typename U, typename Func>
// ~~~~~~~^
为了确保它 将 可以用 U
类型的(左值)参数调用,只需放置一个 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
的参数,那么这不应被视为构造函数定义中的问题,也不在 class 定义本身。这清楚地表明该参数将从某个外部源提供,并且在某个地方您将知道并且将能够验证它是否是否适合可调用。
当然,您不应该试图推断出可调用对象的确切签名。它在实践中毫无用处,很可能意味着您的设计存在缺陷。
也就是说,一旦您最终知道使用了哪种类型的参数,请在此处输入 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;
}
或者,您可以使用 std::enable_if_t
或 requires
:
calculate
SFINAE 友好
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;
}