假设我们有一个模板化类 P,它具有三个模板化参数 A、B 和 C(即
P<A,B,C>
)。是否可以在源文件(而不是标头)中对三个类型列表的笛卡尔积进行单态化(给定 [A1,A2,A2]
、[B1,B2,B3]
和 [C1,C2,C3]
,我希望为 i 提供 P<Ai,Bj,Ck>
) ,j,k 在目标文件的 [1,2,3] 中)?虽然我认为写下我需要的所有单态并不是不合理的,但它很快就会爆炸。我知道最好将模板化函数移动到头文件中并结束,但假设这些函数很大并且需要一段时间才能编译,因此将其移动到头文件不是一个选择,因为成本在开发过程中重新编译它们的次数太多了。
如果解决方案能用 gcc 编译那就太好了<13 (for compatibility with nvcc).
其他问题:我们可以做同样的事情,但在类型上使用谓词函数吗?
我考虑了以下解决方案,但通过检查
nm
,它表明函数是本地的(用t
而不是T
标记)。使用 SEQ_FOR_EACH
似乎也是可行的,但我想(如果可能的话)避免宏和 boost。理想情况下,我希望这些类在顶级范围内可用,而不是在嵌套类中可用(也许可以通过在另一个类中创建类来实现)
// main.cpp
#include <iostream>
#include "lib.hpp"
using namespace H;
int main(){
P<A1,B2,C3> p;
std::cout << p.f(2) << std::endl;
}
// lib.hpp
#include <variant>
#include <stdexcept>
namespace H {
template<typename A, typename B, typename C>
struct P {
size_t f(size_t x) {
return A::value * B::value * C::value * x;
}
};
struct A1 { static constexpr size_t value = 1; };
struct A2 { static constexpr size_t value = 2; };
struct A3 { static constexpr size_t value = 3; };
struct B1 { static constexpr size_t value = 1; };
struct B2 { static constexpr size_t value = 2; };
struct B3 { static constexpr size_t value = 3; };
struct C1 { static constexpr size_t value = 1; };
struct C2 { static constexpr size_t value = 2; };
struct C3 { static constexpr size_t value = 3; };
using Av = std::variant<A1, A2, A3>;
using Bv = std::variant<B1, B2, B3>;
using Cv = std::variant<C1, C2, C3>;
}
// lib.cpp
#include "lib.hpp"
namespace H {
Av make_variantA(size_t x) {
if (x == 1) {
return A1();
} else if (x == 2) {
return A2();
} else if (x == 3) {
return A3();
} else {
throw std::runtime_error("x = " + std::to_string(x) + " is not valid");
}
}
Bv make_variantB(size_t x) {
if (x == 1) {
return B1();
} else if (x == 2) {
return B2();
} else if (x == 3) {
return B3();
} else {
throw std::runtime_error("x = " + std::to_string(x) + " is not valid");
}
}
Cv make_variantC(size_t x) {
if (x == 1) {
return C1();
} else if (x == 2) {
return C2();
} else if (x == 3) {
return C3();
} else {
throw std::runtime_error("x = " + std::to_string(x) + " is not valid");
}
}
void P_mono(size_t a, size_t b, size_t c) {
std::visit([&]<typename A>(A) -> size_t {
return std::visit([&]<typename B>(B) -> size_t {
return std::visit([&]<typename C>(C) -> size_t {
P<A, B, C> p;
return 1;
}, make_variantC(c));
}, make_variantB(b));
}, make_variantA(a));
}
}
cmake_minimum_required(VERSION 3.27)
project(stack_mono)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -I .. -Wall -mcmodel=medium -march=native -Wextra -Wno-register -fPIC -Wfatal-errors")
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer ")
add_library(lib lib.cpp)
add_executable(stack_mono main.cpp)
target_link_libraries(stack_mono lib)
该示例似乎有效,但它总是有效吗?还是我受编译器的支配来决定该函数是否在任何地方都可用?
您正在隐式实例化该类型。隐式实例化有点像内联函数:编译器在链接时消除重复项。
符号链接时间之后发生的事情是 C++ 标准之外的主题。
在实践中,甚至你的隐式实例化也可以被编译掉,因为未使用的隐式实例化在非病态的prpgram中不会产生副作用(例如,也许一些有状态元编程可以检测到它,但有状态元编程被认为是有害的) .
我怀疑您想要的是显式模板实例化,以便其他对象文件不需要查看定义。除了宏或代码生成之外,无法导出模板实例化的计算列表。
https://en.cppreference.com/w/cpp/language/class_template涵盖显式模板实例化。
此时你真的是在与nvcc作战。
您可以创建一个接受 N 个整数并返回笛卡尔积的变体的函数。如果保留该函数,则意味着这些类在某种意义上都存在。
为此,我将从索引开始:
template<std:;size_t n>
using index_t=std::integral_constant<std:;size_t, n>;
接下来定义从 0 到 n-1 的索引变体(称之为
venum<n>
= std::variantconstexpr venum<sizeof...(Is)> r[]={
index_v<Is>...
};
return r[i];
我们现在可以使用模板元编程来编写变体生成器,在一组类型上生成变体(我会将类型包装在
struct tag_t<T>{};
中,这样我们就不必在通用代码中关心它们的属性)。
有类似的类型列表
模板
using cart_result=cartesian_t<
types_t<A1,A2,A3>,
types_t<B1,B2>,
types_3<c1,C2,C3>
>;
产生
types_t<types_t<A1,B1,C1>,…,types_t<A3,B2,C3>>
。
类似地,类型应用程序(fmap)采用类型的类型包和模板,并生成模板实例的类型包。然后另一个应用程序采用类型包和模板,并将类型应用到模板。
结果是
std::variant<P<…>,…,P<…>> getP(int a, int b, int c);
使用您编写一次的类型列表的笛卡尔积生成函数的返回值和函数体。
由于 getP 函数可以从目标文件中导出,因此消除 P 类(即使它们只是隐式实例化)似乎很困难。
请注意:
...
的使用是字面的C++三点,而…
是象征性的填空。跳过了许多细节,因为在智能手机上输入它们很困难,我怀疑OP(以及任何其他进行指数模板代码生成的人)可以通过一些努力来填补空白:我在这里的其他答案中使用了这些技术,我可以如果需要的话,疏通 thdm。
小心:当你要求编译器从模板生成指数数量的代码时,编译器往往会爆炸。很少有人认为导出模板符号长度的 O 表示法是核心编程障碍,但它就在这里。