Home > OS >  Lazy type constraint (implement Rust `Default` trait in c )
Lazy type constraint (implement Rust `Default` trait in c )

Time:10-12

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.

CodePudding user response:

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.

CodePudding user response:

Yes, it can be done with concepts:

#include <concepts>

template<std::default_initializable  T>
struct Box {};

struct Yes{};

struct No{ No(int){}}; // No default ctor
int main()
{
    Box<Yes> y;
    Box<No> n;
}

which outputs:

<source>:14:11: error: template constraint failure for 'template<class T>  requires  default_initializable<T> struct Box'
   14 |     Box<No> n;
      |           ^
<source>:14:11: note: constraints not satisfied
In file included from <source>:1:
/opt/compiler-explorer/gcc-12.2.0/include/c  /12.2.0/concepts: In substitution of 'template<class T>  requires  default_initializable<T> struct Box [with T = No]':
<source>:14:11:   required from here
/opt/compiler-explorer/gcc-12.2.0/include/c  /12.2.0/concepts:136:13:   required for the satisfaction of 'constructible_from<_Tp>' [with _Tp = No]
/opt/compiler-explorer/gcc-12.2.0/include/c  /12.2.0/concepts:141:13:   required for the satisfaction of 'default_initializable<T>' [with T = No]
/opt/compiler-explorer/gcc-12.2.0/include/c  /12.2.0/concepts:137:30: note: the expression 'is_constructible_v<_Tp, _Args ...> [with _Tp = No; _Args = {}]' evaluated to 'false'
  137 |       = destructible<_Tp> && is_constructible_v<_Tp, _Args...>;
      |                              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Box<Yes> will compile without issues. The above is a shorthand for the following which allows to chain more requirements

template<typename  T>
requires std::default_initializable<T>
struct Box { };
  • Related