C++ 标量和强制调用站点可读性?

C++ 标量和强制调用站点可读性?,c++,C++,我想知道目前推荐的通过利用类型系统消除参数歧义和提高函数调用站点可读性的方法是什么。一个简单的例子是 int total(int price, int amount) { return price * total; } 在这种情况下,很难准确地说出像total(10,2)这样的调用意味着什么,即哪个参数是哪个(在这种情况下,由于乘法的可交换性,如果弄乱了顺序,就不会出现错误) 基本上,您可以快速执行以下操作: enum class ScalarTypeTag { PriceTag, Amo

我想知道目前推荐的通过利用类型系统消除参数歧义和提高函数调用站点可读性的方法是什么。一个简单的例子是

int total(int price, int amount) {
  return price * total;
}
在这种情况下,很难准确地说出像
total(10,2)
这样的调用意味着什么,即哪个参数是哪个(在这种情况下,由于乘法的可交换性,如果弄乱了顺序,就不会出现错误)

基本上,您可以快速执行以下操作:

enum class ScalarTypeTag { PriceTag, AmountTag, TotalTag};

template<typename T, ScalarTypeTag K>
struct ScalarWrapper {
  explicit ScalarWrapper(T value) : value_{value} {}
  T value_;
};

using Price = ScalarWrapper<int, ScalarTypeTag::PriceTag>;
using Amount = ScalarWrapper<int, ScalarTypeTag::PriceTag>;
using Total = ScalarWrapper<int, ScalarTypeTag::TotalTag>;

Total ComputeTotal(Price p, Amount n) {
  return Total(p.value_ * n.value_);
}
enum类ScalarTypeTag{PriceTag,AmountTag,TotalTag};
模板
结构ScalarWrapper{
显式ScalarWrapper(T值):值{value}{}
T值;
};
使用Price=ScalarWrapper;
使用量=ScalarWrapper;
使用Total=ScalarWrapper;
计算总额(价格p,金额n){
返回总计(p.value×n.value);
}
这将使调用者很难混淆参数顺序,同时成为零成本抽象,因为编译器将优化包装类型。我基本上对如何做到这一点感兴趣,因此:

  • 最小化用户所需的额外代码

  • 使添加新类型变得简单(没有错误)

  • 是一个零成本的抽象(与用底层类型替换类型相比)

  • 在上面的示例中,我故意引入了一个bug,在复制粘贴后忘记将Amount上的标记更改为AmountTag,因此Amount实际上是与Price相同的类型,因此我们仍然可能意外地弄乱参数顺序,编译器无法捕获它。最好是尽量减少发生这种堵塞的可能性

    换句话说,如何最大限度地利用这种结构,同时最大限度地减少所造成的烦恼

    更新:另一个问题是,这个实现(可能还没有检查)也会阻止像std::vector这样的东西被优化为memcpy而不是循环,所以它不会是完全零成本。

    struct tag_t{constexpr tag_t(){};
    
    struct tag_t {constexpr tag_t(){}};
    
    inline constexpr tag_t price{}
    inline constexpr tag_t amount{};
    inline constexpr tag_t total{};
    
    template<class T, tag_t const* tag>
    struct scalar_t {
      T t;
      constexpr explicit scalar_t( T tin ):t(std::move(tin)) {}
      constexpr scalar_t( scalar_t const& ) = default;
      constexpr scalar_t( scalar_t && ) = default;
      constexpr scalar_t& operator=( scalar_t const& ) = default;
      constexpr scalar_t& operator=( scalar_t && ) = default;
      constexpr scalar_t() = default;
      constexpr ~scalar_t() = default;
    };
    
    template<auto x, tag_t const* tag >
    constexpr auto scalar = scalar_t<std::decay_t<decltype(x)>, tag>(x);
    
    template<tag_t const* tag>
    using iscalar_t = scalar_t<int, tag>;
    
    scalar_t<int, &total> ComputeTotal( scalar_t<int, &price> p, scalar_t<int, &amount> n ) {
      return scalar_t<int, &total>( p.t * n.t );
    }
    
    内联constexpr标签价格{} 内联constexpr标记量{}; 内联constexpr tag_t total{}; 模板 结构标量{ T; constexpr显式标量_t(t tin):t(std::move(tin)){ constexpr scalar\u t(scalar\u t const&)=默认值; constexpr scalar_t(scalar_t&&)=默认值; constexpr scalar\u t&operator=(scalar\u t const&)=默认值; constexpr scalar_t&operator=(scalar_t&&)=默认值; constexpr scalar_t()=默认值; constexpr~scalar_t()=默认值; }; 模板 constexpr auto scalar=标量(x); 模板 使用iscalar\u t=标量\u t; 标量计算总数(标量p,标量n){ 返回标量t(p.t*n.t); }
    打电话的人可以

    int x = 33;
    auto total = ComputeTotal( scalar<7, &price>, iscalar_t<&amount>( x ) );
    
    intx=33;
    自动总计=计算总计(标量,iscalar_t(x));
    
    并使用强制转换传递编译时值或运行时值

    我们不使用类型别名,而是为标记使用指向标记的指针。任何人都可以创建一个新的标量标记,它的constexpr地址就是它的标识

    这允许分布式标量类型生成; 内联constexpr标签价格{} 内联constexpr标记量{}; 内联constexpr tag_t total{}; 模板 结构标量{ T; constexpr显式标量_t(t tin):t(std::move(tin)){ constexpr scalar\u t(scalar\u t const&)=默认值; constexpr scalar_t(scalar_t&&)=默认值; constexpr scalar\u t&operator=(scalar\u t const&)=默认值; constexpr scalar_t&operator=(scalar_t&&)=默认值; constexpr scalar_t()=默认值; constexpr~scalar_t()=默认值; }; 模板 constexpr auto scalar=标量(x); 模板 使用iscalar\u t=标量\u t; 标量计算总数(标量p,标量n){ 返回标量t(p.t*n.t); } 打电话的人可以

    int x = 33;
    auto total = ComputeTotal( scalar<7, &price>, iscalar_t<&amount>( x ) );
    
    intx=33;
    自动总计=计算总计(标量,iscalar_t(x));
    
    并使用强制转换传递编译时值或运行时值

    我们不使用类型别名,而是为标记使用指向标记的指针。任何人都可以创建一个新的标量标记,它的constexpr地址就是它的标识


    这允许分布式标量类型生成。

    看起来这是的任务。看起来这是的任务。