Search code examples
c++c++20implicit-conversionc++-conceptsvisitor-pattern

C++ require function without implicit conversion


I'm using boost::variant to imitate inheritance with value semantics.

There is one class that may be printed:

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

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

And class that may not:

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

Finally, there is "Base" class with implicit cast:

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);
}

The problem is how to check for printable inside of visitor:

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 /*...*/; }
}; 

Requirement (1) is always true due-to implicit conversion to const Base &. How to avoid conversion only in that exact place?


Solution

  • I found a perfect solution that is based on Inline friend definition.

    According to standard:

    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).


    Some other things that may work:

    1. Deleted template function

      void print(auto) = delete;
      

    cons:

    • Boilerplate for every function

    • Forbids all implicit conversions

      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. Change interface

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

    cons:

    • No operators support (Base + Base -> Base::add(Base, Base))
    • Interface a little bit worse
    1. Add traits
      template<typename T> struct Traits {
        static constexpr bool is_printable = true;
      }
      

    cons:

    • Boilerplate for every class and method
    • How to handle implicit conversion (Boolean + Integer)?
    • Closed for extension