编译时函数选择取决于类型大小

Posted

技术标签:

【中文标题】编译时函数选择取决于类型大小【英文标题】:compile-time function choice depending on type size 【发布时间】:2019-11-15 23:12:49 【问题描述】:

我想要一个模板函数以一种特殊的方式复制数据。 如果数据元素类型大小是 4 字节的倍数,则有一种简单的方法,即 (sizeof(T) % 4 == 0):

template <typename T, typename Idx, uint32 dimensions>
void loadData4BWords(T *target, const T *source, const Idx eleCount);

如果不是这种情况,还有一种更复杂的方法来复制数组:

template <typename T, typename Idx, uint32 dimensions>
void loadDataNo4BWords(T *target, const T *source, const Idx eleCount);

如何编写一个调用者模板函数,在编译时做出这个决定并对用户透明?例如:

template <typename T, typename Idx, uint32 dimensions>
void loadData(T *target, const T *source, const Idx eleCount);

应该根据编译时条件multipleOf4BWord = (sizeof(T) % 4 == 0)调用上述两个版本之一。更准确地说,loadData 应该在编译时被翻译成上述两个版本之一。

【问题讨论】:

什么 C++ 版本可用? GCC 7 和 MSVC 19.23 支持的交集。 【参考方案1】:

自 C++17 起,您可以使用 if constexpr 调用其中一个:

template <typename T, typename Idx, uint32 dimensions>
void loadData(T *target, const T *source, const Idx eleCount) 
    if constexpr(sizeof(T) % 4 == 0)
        loadData4BWords<T, Idx, dimensions>(target, source, eleCount);
    else
        loadDataNo4BWords<T, Idx, dimensions>(target, source, eleCount);

if 相比,if constexpr 在编译时进行测试,只编译匹配的分支。

【讨论】:

感谢您的快速响应和这个不错的解决方案! :)【参考方案2】:

if constexpr 是最好的。但是老派标签调度也有效,并且在某些情况下可能会更清晰(特别是 C++17 之前的版本),所以我将在讨论中贡献这个选项:

template <typename T, typename Idx, uint32 dimensions>
void loadData(T *target, const T *source, const Idx eleCount, std::true_type)

    loadData4BWords(target, source, eleCount);


template <typename T, typename Idx, uint32 dimensions>
void loadData(T *target, const T *source, const Idx eleCount, std::false_type)

    loadDataNo4BWords(target, source, eleCount);


template <typename T, typename Idx, uint32 dimensions>
void loadData(T *target, const T *source, const Idx eleCount)

    loadData(target, source, eleCount,
        std::integral_constant<bool, sizeof(T) % 4 == 0>);

【讨论】:

【参考方案3】:

您可以使函数成为结构的成员并使用部分模板特化:

template<typename T, bool is_even_multiple=0==(sizeof(T)%4)>
struct load_data_helper;

template<typename T>
struct load_data_helper<T, true>

template<uint32_t Dimensions, typename Idx>
static void apply(T * dest, T const * src, Idx const & index)
 ... 
;
template<typename T>
struct load_data_helper<T, false>

template<uint32_t Dimensions, typename Idx>
static void apply(T * dest, T const * src, Idx const & index)
 ... 
;

template<uint32_t Dimensions, typename T, typename Idx>
void load_data(T * dest, T const * src, Idx const & index)

load_data_helper<T>::apply<Dimensions>(dest, src, index);

然后调用将是:

load_data<3>(dest, src, index);

注意我并没有实际编译上面的代码,所以提供的代码可能有错误,但概述的方法应该可以工作。

【讨论】:

感谢您的快速响应!如果我不能使用 constexpr if,我会寻求这样的解决方案。【参考方案4】:

在 C++17 if constexpr 中,正如不均匀标记所建议的那样,是最简单和更清晰的解决方案 (恕我直言)。

在 C++17(C++11 和 C++14)之前,您可以使用重载和 SFINAE(使用 std::enable_if

我的意思是...你可以简化很多问题,如果,而不是 loadData4BWords()loadDataNo4BWords() 功能曾经启用,你创建一个 loadData() 仅当 0u == sizeof(T) % 4u 启用(loadData4BWords() 等效)和loadData() 仅在 0u != sizeof(T) % 4uloadDataNo4BWords() 等效项)时启用。

以下是完整的 C++11 工作示例(简化:只有一个参数)

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<0u == sizeof(T) % 4u>::type loadData (T *)
  std::cout << "4 version" << std::endl; 

template <typename T>
typename std::enable_if<0u != sizeof(T) % 4u>::type loadData (T *)
  std::cout << "no 4 version" << std::endl; 


int main ()
 
   char  ch;
   int   i;

   loadData(&ch);
   loadData(&i);
 

在 C++14(和 C++17,如果您愿意)中,您可以使用 std::enable_if_t 来简化一点

template <typename T>
std::enable_if_t<0u == sizeof(T) % 4u> loadData (T *)
  std::cout << "4 version" << std::endl; 

template <typename T>
std::enable_if_t<0u != sizeof(T) % 4u> loadData (T *)
  std::cout << "no 4 version" << std::endl; 

ps:另请参阅 Jeff Garrett 的回答中的标签调度方式。

【讨论】:

以上是关于编译时函数选择取决于类型大小的主要内容,如果未能解决你的问题,请参考以下文章

c ++ unordered_set .find方法不会编译,给出“表达式必须有类类型”的错误

Flex:传递函数和检查参数

定义表示不透明 C 结构的 Rust 类型的交叉编译安全方法,其大小在编译时已知

乘以子类型时编译器选择了错误的运算符*

TypeScript:如何在编译时声明固定大小的数组以进行类型检查

编译时泛型类型大小检查