c++type-constraints

Lazy type constraint (implement Rust `Default` trait in c++)


In Rust you can implement traits for structs and each implementation has own type constraint. (for who may does not familiar with rust, you can consider a "trait" as a "base class" and "implementation" as "inheritance"). look at this example:

// our struct has an item of type mutable T pointer
struct Box<T> {
    inner: mut* T
} 

// implementing `Default` trait for 
// box. in this implementation, type 
// constraint is: `T: Default`. 
// it means the inner type also must 
// implements Default. 
impl <T: Default> for Box<T> {
    fn default() -> Self {
        return Box::new(T::default());
    }
}

the note in this example is there is no need T: Default to be applied until you use Box::default() in your code.

well it is possible to do like this in cpp? I'm familiar with how we can constraint types in cpp and this is not my problem. I want to know is there some way to lazy type constrain (maybe a better description) in cpp when I define a class?

I know it is possible with if constexpr or static_assert. but this way is not beautiful. I want a template or concept solution ( I mean what applies to function signature in fact) if possible. thank you for any guidance.


Solution

  • I am not sure I follow exactly what you want (I don't really know Rust), but you can constrain member functions individually:

    template<typename T>
    concept Default = requires {
        { T::default_() } -> std::same_as<T>;
    };
    
    template<typename T>
    struct Box {
        static Box default_()
        requires Default<T> {
            //...
        }
        //...
    };
    

    Now Box itself has no requirements on T, but to use Box::default() will require T to satisfy the Default concept.

    However, it would basically work without the constraint as well. If Box<T>::default calls T::default() and the latter is not well-formed the code will fail to compile if and only if Box<T>::default is actually used in a way that requires it to be defined (i.e. called or pointer/reference to it taken). But without the requires clause e.g. Default<Box<T>> would always report true.

    And of course in C++ we would use the default constructor instead of a static member function called default to construct the object. The same approach applies to constructors though and there is already the concept std::default_initializable for that in the standard library.

    As far as I understand, Rust traits do not really overlap fully with either C++ (abstract) base classes nor concepts though. The approach above will not allow for runtime polymorphism on types satisfying Default. For that an abstract base class with the interface functions as non-static pure virtual member functions should be used instead.

    However, the trait Default only imposes a requirement on a static member function, so that it shouldn't be relevant to runtime polymorphism, but only compile-time properties of the type, which is what concepts are for, although in contrast to Rust you don't declare a type to implement a trait. Instead the concept describes requirements which a type needs to satisfy to satisfy the concept.