I am currently writing a game engine in C++ with Vulkan and right now I am working on boiling down my spaghetti code into something more intuitive. As part of this process, I generalized a descriptor struct to create several components needed by the engine, and the struct is inherited by several objects with different types.
This is not a problem between my SSBO and UBO objects because they both have a member variable of the VkDescriptorBufferInfo type. However, my Texture object differs in that the member variable is of the VkDescriptorImageInfo type.
These two types are used by the VkWriteDescriptorSet object, whose definition in the "vulkan_core.h" header is:
typedef struct VkWriteDescriptorSet {
VkStructureType sType;
const void* pNext;
VkDescriptorSet dstSet;
uint32_t dstBinding;
uint32_t dstArrayElement;
uint32_t descriptorCount;
VkDescriptorType descriptorType;
const VkDescriptorImageInfo* pImageInfo; //to be provided by my Texture objects
const VkDescriptorBufferInfo* pBufferInfo; //to be provided by my UBO/SSBO objects
const VkBufferView* pTexelBufferView;
} VkWriteDescriptorSet;
My attempt at generalizing the creation of the VkWriteDescriptorSet object is as follows:
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 };
/* Here I try to differentiate how the VkWriteDescriptorSet object is created */
if (std::is_same<T, VkDescriptorImageInfo>::value) {
writeInfo.pImageInfo = &bufferInfo[dst[1]]; //requires T == VkDescriptorImageInfo
}
else {
writeInfo.pBufferInfo = &bufferInfo[dst[1]]; //requires T == VkDescriptorBufferInfo
}
/* Then the function fills out the rest of the object */
return writeInfo;
}
The above is called within the following function:
void writeSets(uint32_t bindingCount) {
// The type of the vector below depends on the calling object (UBO/SSBO or Texture)
std::vector</* VkDescriptorBufferInfo or VkDescriptorImageInfo */> bufferInfo(bindingCount);
std::vector<VkWriteDescriptorSet> descriptorWrites(bindingCount);
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
/* loop does stuff */
for (size_t j = 0; j < bindingCount; j++) {
/* loop does more stuff */
descriptorWrites[j] = writeSet(bufferInfo, { i, j }); // the generalized function is called
}
vkUpdateDescriptorSets(VkGPU::device, bindingCount, descriptorWrites.data(), 0, nullptr);
}
}
Unfortunately, my program will not compile and returns the following error:
Severity: Error
Line: 27
Code: C2440
Description: '=': cannot convert from 'const _Ty *' to 'const VkDescriptorImageInfo *'
Which finally brings us to my question, how do I use template parameters within "if" and "switch" statements?
Edit:
One of the other methods I attempted to generalize the function was with a switch statement using the VkDescriptorType enum:
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 };
/* Here I try to differentiate how the VkWriteDescriptorSet object is created */
switch (type) {
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
writeInfo.pImageInfo = &bufferInfo[dst[1]]; //requires T == VkDescriptorImageInfo
default:
writeInfo.pBufferInfo = &bufferInfo[dst[1]]; //requires T == VkDescriptorBufferInfo
}
/* Then the function fills out the rest of the object */
return writeInfo;
}
>Solution :
C++17
If you are using C++17, the solution is quite simple. You can turn if into if constexpr:
VkWriteDescriptorSet writeInfo { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
if constexpr (std::is_same_v<T, VkDescriptorImageInfo>) {
writeInfo.pImageInfo = &bufferInfo[dst[1]];
}
else {
// Optional assertion; ensures that were aren't working with some third type.
// This also documents our type expectation.
static_assert(std::is_same_v<T, VkDescriptorBufferInfo>);
writeInfo.pBufferInfo = &bufferInfo[dst[1]];
}
Note that there is no such thing as switch constexpr, however, you can simply write a long chain of if constexpr ... else if constexpr statements to achieve the same effect.
Pre-C++17
Prior to C++17, you could simply write two overloads:
inline VkWriteDescriptorSet writeSet(const std::vector<VkDescriptorImageInfo>& bufferInfo,
std::array<size_t,2> dst) {
/* ... */
}
inline VkWriteDescriptorSet writeSet(const std::vector<VkDescriptorBufferInfo>& bufferInfo,
std::array<size_t,2> dst) {
/* ... */
}
There isn’t much code duplication, so it should be fine in this case. If the functions are larger, consider solutions such as full or partial specializations of templates, just for the part that is different and depends on T being a specific type.