如何在if/switch语句中结合C++模板参数处理Vulkan描述符?
我用C++结合Vulkan开发游戏引擎,正在重构代码提升可读性。我泛化了一个描述符结构体供多类对象继承:UBO/SSBO对象包含VkDescriptorBufferInfo类型成员,Texture对象则包含VkDescriptorImageInfo类型成员,这两种类型都需要用来填充VkWriteDescriptorSet结构体,其定义如下:
typedef struct VkWriteDescriptorSet { VkStructureType sType; const void* pNext; VkDescriptorSet dstSet; uint32_t dstBinding; uint32_t dstArrayElement; uint32_t descriptorCount; VkDescriptorType descriptorType; const VkDescriptorImageInfo* pImageInfo; // Texture对象提供 const VkDescriptorBufferInfo* pBufferInfo; // UBO/SSBO对象提供 const VkBufferView* pTexelBufferView; } VkWriteDescriptorSet;
我尝试编写模板函数统一创建VkWriteDescriptorSet:
template <typename T> inline VkWriteDescriptorSet writeSet(const std::vector<T>& bufferInfo, std::array<size_t,2> dst) { VkWriteDescriptorSet writeInfo { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; if (std::is_same<T, VkDescriptorImageInfo>::value) { writeInfo.pImageInfo = &bufferInfo[dst[1]]; // 仅T为VkDescriptorImageInfo时合法 } else { writeInfo.pBufferInfo = &bufferInfo[dst[1]]; // 仅T为VkDescriptorBufferInfo时合法 } // 填充其他字段... return writeInfo; }
但编译时报错:
Severity: Error Line: 27 Code: C2440 Description: '=': cannot convert from 'const _Ty *' to 'const VkDescriptorImageInfo *'
之后我尝试用VkDescriptorType枚举做switch分支,同样遇到类型转换错误:
template <typename T> inline VkWriteDescriptorSet writeSet(const std::vector<T>& bufferInfo, std::array<size_t,2> dst) { VkWriteDescriptorSet writeInfo { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; switch (type) { case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: writeInfo.pImageInfo = &bufferInfo[dst[1]]; // 仅T=VkDescriptorImageInfo合法 default: writeInfo.pBufferInfo = &bufferInfo[dst[1]]; // 仅T=VkDescriptorBufferInfo合法 } // 填充其他字段... return writeInfo; }
需要解决的核心问题:如何在模板函数中根据类型或枚举正确分支,避免类型转换错误?
错误原因
普通if或switch属于运行时分支,编译器会检查所有分支的代码合法性,不管条件是否为真。比如当T是VkDescriptorBufferInfo时,writeInfo.pImageInfo = &bufferInfo[dst[1]]这行代码仍会被编译,此时const VkDescriptorBufferInfo*无法转换成const VkDescriptorImageInfo*,直接触发编译错误。
解决方案
1. 模板特化
直接为两种目标类型分别实现模板函数,编译器会根据传入的T自动匹配对应的特化版本:
// 基础模板(留空或添加断言,限制仅支持指定类型) template <typename T> inline VkWriteDescriptorSet writeSet(const std::vector<T>& bufferInfo, std::array<size_t,2> dst); // VkDescriptorImageInfo的特化实现 template <> inline VkWriteDescriptorSet writeSet<VkDescriptorImageInfo>(const std::vector<VkDescriptorImageInfo>& bufferInfo, std::array<size_t,2> dst) { VkWriteDescriptorSet writeInfo { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; writeInfo.pImageInfo = &bufferInfo[dst[1]]; // 填充其他字段... return writeInfo; } // VkDescriptorBufferInfo的特化实现 template <> inline VkWriteDescriptorSet writeSet<VkDescriptorBufferInfo>(const std::vector<VkDescriptorBufferInfo>& bufferInfo, std::array<size_t,2> dst) { VkWriteDescriptorSet writeInfo { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; writeInfo.pBufferInfo = &bufferInfo[dst[1]]; // 填充其他字段... return writeInfo; }
2. C++17 constexpr if
使用constexpr if实现编译时分支,编译器会根据模板参数T的类型,仅编译符合条件的分支,忽略另一分支的代码:
template <typename T> inline VkWriteDescriptorSet writeSet(const std::vector<T>& bufferInfo, std::array<size_t,2> dst) { VkWriteDescriptorSet writeInfo { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; if constexpr (std::is_same_v<T, VkDescriptorImageInfo>) { writeInfo.pImageInfo = &bufferInfo[dst[1]]; } else if constexpr (std::is_same_v<T, VkDescriptorBufferInfo>) { writeInfo.pBufferInfo = &bufferInfo[dst[1]]; } else { // 非法类型触发编译断言 static_assert(false, "不支持的描述符信息类型"); } // 填充其他字段... return writeInfo; }
3. 标签分发(Tag Dispatch)
利用重载函数和标签类,让编译器在编译时自动选择正确的重载实现:
// 定义标签类 struct ImageInfoTag {}; struct BufferInfoTag {}; // 为每种类型绑定对应的标签 template <typename T> struct DescriptorTag; template <> struct DescriptorTag<VkDescriptorImageInfo> { using type = ImageInfoTag; }; template <> struct DescriptorTag<VkDescriptorBufferInfo> { using type = BufferInfoTag; }; // 针对不同标签的重载实现 inline VkWriteDescriptorSet writeSetImpl(const std::vector<VkDescriptorImageInfo>& bufferInfo, std::array<size_t,2> dst, ImageInfoTag) { VkWriteDescriptorSet writeInfo { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; writeInfo.pImageInfo = &bufferInfo[dst[1]]; // 填充其他字段... return writeInfo; } inline VkWriteDescriptorSet writeSetImpl(const std::vector<VkDescriptorBufferInfo>& bufferInfo, std::array<size_t,2> dst, BufferInfoTag) { VkWriteDescriptorSet writeInfo { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; writeInfo.pBufferInfo = &bufferInfo[dst[1]]; // 填充其他字段... return writeInfo; } // 对外暴露的模板接口 template <typename T> inline VkWriteDescriptorSet writeSet(const std::vector<T>& bufferInfo, std::array<size_t,2> dst) { using Tag = typename DescriptorTag<T>::type; return writeSetImpl(bufferInfo, dst, Tag{}); }
结合VkDescriptorType枚举的分支优化
如果需要用VkDescriptorType枚举分支,需确保枚举值与模板参数T强绑定,可通过静态断言避免类型不匹配:
template <typename T> inline VkWriteDescriptorSet writeSet(const std::vector<T>& bufferInfo, std::array<size_t,2> dst, VkDescriptorType descriptorType) { VkWriteDescriptorSet writeInfo { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; writeInfo.descriptorType = descriptorType; if constexpr (std::is_same_v<T, VkDescriptorImageInfo>) { static_assert( descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER || descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, "VkDescriptorImageInfo与传入的描述符类型不匹配" ); writeInfo.pImageInfo = &bufferInfo[dst[1]]; } else if constexpr (std::is_same_v<T, VkDescriptorBufferInfo>) { static_assert( descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER || descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, "VkDescriptorBufferInfo与传入的描述符类型不匹配" ); writeInfo.pBufferInfo = &bufferInfo[dst[1]]; } else { static_assert(false, "不支持的描述符信息类型"); } // 填充其他字段... return writeInfo; }
内容的提问来源于stack exchange,提问作者ModernEraCaveman

