C++ 需要没有隐式转换的函数

C++ require function without implicit conversion

我正在使用 boost::variant 来模拟具有值语义的继承。

有一个class可以打印:

struct Printable { /* ... */ };

void print(const Printable &) { /* ... */ }

而class可能不会:

struct NotPrintable { /* ... */ };

最后,有隐式转换的“Base”class:

struct Base : boost::variant<Printable, NotPrintable>
{
    Base(const Printable &) {}    // constructor for implicit cast
    Base(const NotPrintable &) {} // constructor for implicit cast
};

// Print, if printable, throw exception, if not
void print(const Base &base) 
{ 
    Printer printer{};
    base.apply_visitor(printer);
}

问题是如何检查访问者内部是否可打印:

struct Printer
{
   using result_type = void;

   // If printable 
   template<typename PrintableType> requires 
   requires(const PrintableType &v) { {print(v)}; }  // (1)
   void operator()(const PrintableType &v) { print(v); }

   // If not printable
   void operator()(const auto &v) { throw /*...*/; }
}; 

由于隐式转换为 const Base &,要求 (1) 始终为真。如何避免仅在那个确切位置进行转换?

正如@Jarod42在评论中所说,为了确保不发生隐式转换,您需要定义一个模板print()函数来“吸收”其他类型(NotPrintable在您的示例)并将其设置为 delete

void print(const auto&) = delete;

那么printable概念可以定义为

template<class T>
concept printable = requires (const T& x) { print(x); };

要求表达式 print(x) 为 well-formed.

T的类型为PrintableBase时,表达式print(x)有效,因为你为它们定义了对应的print()函数.当T的类型为NotPrintable或其他类型时,将调用被删除的print(),使得表达式ill-formed,因此不满足约束。

那你可以用这个概念来约束Printer::operator()

struct Printer {
  using result_type = void;

  // If printable 
  template<printable PrintableType>
  void operator()(const PrintableType& v) { print(v); }

  // If not printable
  void operator()(const auto&) { throw /*...*/; }
};

注意,由于print(const auto&)可以实例化为any类型,这将禁止all隐式转换,但我们可以通过向其添加约束仍然允许一些隐式转换

#include <concepts>

struct Boolean { };
struct Integer {
  Integer();
  Integer(Boolean);
  friend Integer operator+(Integer, Integer);
};
struct String { };

template<class T, class U>
  requires (!std::convertible_to<U, T>)
void operator+(T, U) = delete;

int main() {
  Integer{} + Integer{}; // OK
  Integer{} + String{};  // ERROR (as expected)
  Integer{} + Boolean{}; // OK
};

我找到了一个完美的 solution,它基于 内联好友定义

根据标准:

Such a function is implicitly an inline function (10.1.6). A friend function defined in a class is in the (lexical) scope of the class in which it is defined. A friend function defined outside the class is not (6.4.1).


其他一些可能有用的东西:

  1. 删除模板函数

    void print(auto) = delete;
    

缺点:

  • 每个函数的样板

  • 禁止所有隐式转换

    struct Boolean;
    
    struct String;
    
    struct Integer
    {
      Integer(Boolean);
    
      friend Integer operator+(Integer, Integer);
    }
    
    template<class T, class U>
    requires (!std::convertible_to<U, T>)
    void operator+(T, U) = delete;
    
    Integer + Integer // OK
    Integer + String  // ERROR (as expected)
    Integer + Boolean // OK
    
  1. 更改界面

    struct Base : /* ... */
    {
      /* ... */
    
      static void print(Base);
    }
    

缺点:

  • 不支持运算符 (Base + Base -> Base::add(Base, Base))
  • 界面差了点
  1. 添加特征
    template<typename T> struct Traits {
      static constexpr bool is_printable = true;
    }
    

缺点:

  • 每个 class 和方法
  • 的样板
  • 如何处理隐式转换(Boolean + Integer)?
  • 延期关闭