将 c++ 类单态化为类型的笛卡尔积

问题描述 投票:0回答:1

假设我们有一个模板化类 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++ templates monomorphism
1个回答
0
投票

您正在隐式实例化该类型。隐式实例化有点像内联函数:编译器在链接时消除重复项。

符号链接时间之后发生的事情是 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::variant,…,index_t>` - 这样做是留给读者的练习);反过来,您可以创建一个从运行时索引到编译时索引的工厂:

constexpr venum<sizeof...(Is)> r[]={
  index_v<Is>...
};
return r[i];

我们现在可以使用模板元编程来编写变体生成器,在一组类型上生成变体(我会将类型包装在

struct tag_t<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 表示法是核心编程障碍,但它就在这里。

© www.soinside.com 2019 - 2024. All rights reserved.