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