Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Concepts.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Common_Concept_h_
5#define _Stroika_Foundation_Common_Concept_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <chrono>
10#include <concepts>
11#include <functional> // needed for std::equal_to
12#include <iterator> // needed for std::begin/std::end calls
13#include <memory>
14#include <optional>
15#include <type_traits>
16#include <variant>
17
18#include "Stroika/Foundation/Common/Common.h"
19#include "Stroika/Foundation/Common/ConceptsBase.h"
21
22/*
23 * \file
24 * Miscellaneous type traits and concepts for metaprogramming
25 *
26 * \note Code-Status: <a href="Code-Status.md#Alpha">Alpha</a>
27 *
28 *
29 USEFUL EXAMPLE:
30 template <IStdPathLike2UNICODEString TOSTRINGABLE>
31 explicit String (TOSTRINGABLE&& s)
32 requires (
33 not IBasicUNICODEStdString<remove_cvref_t<TOSTRINGABLE>> and
34 not requires (TOSTRINGABLE t) {
35 {
36 []<IUNICODECanUnambiguouslyConvertFrom T1> (const T1*) {}(t)
37 };
38 } and
39 not requires (TOSTRINGABLE t) {
40 {
41 []<IUNICODECanUnambiguouslyConvertFrom T1> (const span<const T1>&) {}(t)
42 };
43 } and
44 not requires (TOSTRINGABLE t) {
45 {
46 []<IStdBasicStringCompatibleCharacter T1> (const basic_string_view<T1>&) {}(t)
47 };
48 })
49#if qCompilerAndStdLib_RequiresNotMatchInlineOutOfLineForTemplateClassBeingDefined_Buggy
50 : String{mkSTR_ (forward<TOSTRINGABLE> (s))} {}
51#else
52 ;
53#endif
54
55 */
56
58
59 /**
60 * \brief ClassNotFinal
61 */
62 template <typename T>
63 concept ClassNotFinal = not is_final_v<T>;
64
65 /**
66 * \brief concept true if integral or floating-point type 'T'.
67 *
68 * \note NOT marked true for arithmetic-like types, like big-num package (perhaps provide another concept for this).
69 *
70 * \note Not sure why not provided by std c++
71 */
72 template <typename T>
73 concept IBuiltinArithmetic = is_arithmetic_v<T>;
74
75 /**
76 * \brief like std::invocable concept, except also requires the invocation doesn't raise exceptions
77 */
78 template <typename F, typename... Args>
79 concept INoThrowInvocable = invocable<F, Args...> and requires (F f, Args... args) {
80 { noexcept (f (args...)) };
81 };
82
83 /**
84 * \par Example Usage
85 * \code
86 * static_assert (invocable_r<decltype ([] (int) { return ""; }), const char*, int>);
87 * static_assert (invocable_r<decltype ([] (char*, char*) {}), void, char*, char*>);
88 * \endcode
89 *
90 * \note used STL-style name since so closely related to invocable - which is part of the standard library.
91 */
92 template <typename F, typename R, typename... Args>
93 concept invocable_r = invocable<F, Args...> && convertible_to<invoke_result_t<F, Args...>, R>;
94 static_assert (invocable_r<decltype ([] (int) { return ""; }), const char*, int>);
95 static_assert (invocable_r<decltype ([] (char*, char*) {}), void, char*, char*>);
96
97 // From https://stackoverflow.com/questions/74383254/concept-that-models-only-the-stdchrono-duration-types
98 template <typename T>
99 concept IDuration =
100 requires { []<typename Rep, typename Period> (type_identity<chrono::duration<Rep, Period>>) {}(type_identity<T> ()); };
101 static_assert (not IDuration<float>);
102
103 // From https://stackoverflow.com/questions/74383254/concept-that-models-only-the-stdchrono-duration-types
104 template <typename T>
105 concept ITimePoint =
106 requires { []<typename CLOCK, typename DURATION> (type_identity<chrono::time_point<CLOCK, DURATION>>) {}(type_identity<T> ()); };
107 static_assert (not ITimePoint<float>);
108
109 /**
110 * \brief Concept: trivial shorthand for variadic same_as A or same_as B, or ...
111 *
112 * \par Example Usage
113 * \code
114 * template <typename T>
115 * concept IBasicUNICODECodePoint = same_as<remove_cv_t<T>, char8_t> or same_as<remove_cv_t<T>, char16_t> or same_as<remove_cv_t<T>, char32_t>;
116 *
117 * template <typename T>
118 * concept IBasicUNICODECodePoint = Common::IAnyOf<remove_cv_t<T>, char8_t, char16_t, char32_t>;
119 * \endcode
120 */
121 template <typename T, typename... U>
122 concept IAnyOf = (same_as<T, U> or ...);
123
124 /**
125 * \brief Concept: std::is_trivially_copyable_v
126 */
127 template <typename T>
128 concept trivially_copyable = is_trivially_copyable_v<T>;
129
130 /**
131 * A template which ignores its template arguments, and always returns true_type;
132 * NOT crazy - helpful is template metaprogramming.
133 */
134 template <typename...>
135 using True = true_type;
136
139
140 /**
141 * \brief equality_comparable_with, but less strict - just checks if it can be equality compared!
142 *
143 * INSPIRATION: https://godbolt.org/z/qevGWKan4
144 * static_assert (equality_comparable_with<nullopt_t, optional<int>>); // note this fails
145 */
146 template <class _Ty1, class _Ty2>
147 concept Weak_Equality_Comparable_With = requires (const remove_reference_t<_Ty1>& __x, const remove_reference_t<_Ty2>& __y) {
148 { __x == __y } -> Boolean_testable;
149 { __x != __y } -> Boolean_testable;
150 };
151 static_assert (not equality_comparable_with<nullopt_t, optional<int>>);
153
154 /**
155 */
156 template <typename OT>
157 concept IOptional = same_as<remove_cvref_t<OT>, optional<typename OT::value_type>>;
158 static_assert (IOptional<optional<int>>);
159 static_assert (not IOptional<int>);
160
161 namespace Private_ {
162#if qCompilerAndStdLib_template_concept_matcher_requires_Buggy
163 template <typename T1, typename T2 = void>
164 struct is_shared_ptr_ : false_type {};
165 template <typename T1>
166 struct is_shared_ptr_<shared_ptr<T1>> : true_type {};
167 template <typename T1, typename T2 = void>
168 struct is_pair_ : false_type {};
169 template <typename T1, typename T2>
170 struct is_pair_<pair<T1, T2>> : true_type {};
171 template <typename... ARGS>
172 struct is_variant_ : false_type {};
173 template <typename... ARGS>
174 struct is_variant_<variant<ARGS...>> : true_type {};
175#endif
176 }
177
178 /**
179 * \brief return true iff argument type T, is std::pair<a,b> for some a/b types
180 */
181 template <typename T>
182 concept IPair =
183#if qCompilerAndStdLib_template_concept_matcher_requires_Buggy
184 Private_::is_pair_<T>::value
185#else
186 requires (T t) {
187 {
188 []<typename T1, typename T2> (pair<T1, T2>) {}(t)
189 };
190 }
191#endif
192 ;
193
194 /**
195 * \brief return true iff argument type T, is std::shared_ptr<A> for some A types
196 */
197 template <typename T>
198 concept ISharedPtr =
199#if qCompilerAndStdLib_template_concept_matcher_requires_Buggy
200 Private_::is_shared_ptr_<T>::value
201#else
202
203 requires (T t) {
204 {
205 []<typename T1> (shared_ptr<T1>) {}(t)
206 };
207 }
208#endif
209 ;
210 static_assert (ISharedPtr<shared_ptr<int>>);
211 static_assert (not ISharedPtr<int>);
212
213 namespace Private_ {
214 template <typename T, size_t N>
215 concept has_tuple_element = requires (T t) {
216 typename tuple_element_t<N, remove_const_t<T>>;
217 { get<N> (t) } -> convertible_to<const tuple_element_t<N, T>&>;
218 };
219 }
220
221 /**
222 * \brief Concept ITuple<T> check if T is a tuple.
223 *
224 * based on https://stackoverflow.com/questions/68443804/c20-concept-to-check-tuple-like-types
225 */
226 template <typename T>
227 concept ITuple = !is_reference_v<T> && requires (T t) {
228 typename tuple_size<T>::type;
229 requires derived_from<tuple_size<T>, integral_constant<size_t, tuple_size_v<T>>>;
230 } && []<size_t... N> (index_sequence<N...>) { return (Private_::has_tuple_element<T, N> && ...); }(make_index_sequence<tuple_size_v<T>> ());
231
232 /**
233 * \brief - detect if T is a std::variant<> type.
234 */
235 template <typename T>
236 concept IVariant =
237#if qCompilerAndStdLib_template_concept_matcher_requires_Buggy
238 Private_::is_variant_<T>::value
239#else
240 requires (T t) {
241 {
242 []<typename... TYPES> (variant<TYPES...>) {}(t)
243 };
244 }
245#endif
246 ;
247 static_assert (not IVariant<int>);
248 static_assert (IVariant<variant<int>>);
249
250 /**
251 * Concepts let you construct a 'template' of one arg from one with two args, but class, and variable templates don't allow
252 * this; but this magic trick of double indirection does allow it. And cannot use concepts as template arguments to another template
253 * sadly, so need this trick...
254 *
255 * The 'test' here just invokes convertible_to<TEST_ARGUMENT, T>
256 */
257 template <typename T>
259 template <typename TEST_ARGUMENT>
260 using Test = conditional_t<convertible_to<TEST_ARGUMENT, T>, true_type, false_type>;
261 };
262
263 /**
264 * Concepts let you construct a 'template' of one arg from one with two args, but class, and variable templates don't allow
265 * this; but this magic trick of double indirection does allow it. And cannot use concepts as template arguments to another template
266 * sadly, so need this trick...
267 *
268 * The 'test' here just invokes constructible_from<TEST_ARGUMENT, T>
269 */
270 template <typename T>
272 template <typename TEST_ARGUMENT>
273 using Test = conditional_t<constructible_from<TEST_ARGUMENT, T>, true_type, false_type>;
274 };
275
276 /**
277 * \brief Concept checks if the given type T has a const size() method which can be called to return a size_t.
278 *
279 * \par Example Usage
280 * \code
281 * if constexpr (IHasSizeMethod<T>) {
282 * T a{};
283 * return a.size ();
284 * }
285 * \endcode
286 */
287 template <typename T>
288 concept IHasSizeMethod = requires (const T& t) {
289 { t.size () } -> convertible_to<size_t>;
290 };
291
292 namespace Private_ {
293 template <typename T>
294 concept HasEq_ = requires (T t) {
295 { t == t } -> convertible_to<bool>;
296 };
297 template <typename T>
298 constexpr inline bool HasEq_v_ = HasEq_<T>;
299 template <typename T, typename U>
300 constexpr inline bool HasEq_v_<pair<T, U>> = HasEq_v_<T> and HasEq_v_<U>;
301 template <typename... Ts>
302 constexpr inline bool HasEq_v_<tuple<Ts...>> = (HasEq_v_<Ts> and ...);
303 template <typename T>
304 constexpr bool HasUsableEqualToOptimization ()
305 {
306 // static_assert (Common::IOperatorEq<remove_cvref_t<T>> and ! equality_comparable<T>);
307 // static_assert (not Common::IOperatorEq<T> and equality_comparable<T>);
308 // static_assert (Common::IOperatorEq<remove_cvref_t<T>> == equality_comparable<T>);
309 // @todo figure out why Private_::HasEq_v_ needed and cannot use equality_comparable
310 if constexpr (Private_::HasEq_v_<T>) {
311 struct EqualToEmptyTester_ : equal_to<T> {
312 int a;
313 };
314 // leverage empty base class optimization to see if equal_to contains any real data
315 return sizeof (EqualToEmptyTester_) == sizeof (int);
316 }
317 return false;
318 }
319 }
320
321 /**
322 * Check if equal_to<T> is both well defined, and contains no data. The reason it matters that it contains no data, is because
323 * then one instance is as good as another, and it need not be passed anywhere, opening an optimization opportunity.
324 */
325 template <typename T>
326 concept IEqualToOptimizable = equality_comparable<T> and Private_::HasUsableEqualToOptimization<T> ();
327
328 /**
329 * \brief Concept checks if the given type T has a value_type (type) member
330 *
331 * \par Example Usage
332 * \code
333 * if constexpr (IHasValueType<T>) {
334 * typename T::value_type x;
335 * }
336 * \endcode
337 *
338 * \note this replaces Stroika v2.1 constexpr inline bool has_value_type_v template variable
339 */
340 template <typename T>
341 concept IHasValueType = requires (T t) { typename T::value_type; };
342
343 namespace Private_ {
344 template <typename T, typename = void>
345 struct ExtractValueType {
346 using type = void;
347 };
348 template <IHasValueType T>
349 struct ExtractValueType<T> {
350 using type = typename T::value_type;
351 };
352 template <typename T>
353 struct ExtractValueType<const T*, void> {
354 using type = T;
355 };
356 template <typename T>
357 struct ExtractValueType<T*, void> {
358 using type = T;
359 };
360 }
361
362 /**
363 * \brief Extract the type of elements in a container, or returned by an iterator (value_type) or void it there is no value_type
364 *
365 * \note when known if argument is container or iterator, use std::iter_value_t, or std::ranges::range_value_t
366 *
367 * If the given T has a field value_type, return it; returns void if T has no value_type
368 *
369 * NOTE - similar to std::ranges::range_value_t or std::iter_value_t except works with other types.
370 */
371 template <typename T>
372 using ExtractValueType_t = typename Private_::ExtractValueType<remove_cvref_t<T>>::type;
373
374 /**
375 * @brief check T has had remove_cvref_t called on it (e.g. ICVRefTd<const string&> is string)
376 */
377 template <typename T>
378 concept ICVRefTd = same_as<T, remove_cvref_t<T>>;
379
380 /**
381 * from https://stackoverflow.com/questions/32785105/implementing-a-switch-type-trait-with-stdconditional-t-chain-calls
382 * \par Example Usage
383 * \code
384 * using Type = Select_t<Case<false, void>,
385 * Case<false, int>,
386 * Case<true, std::string>>;
387 * \endcode
388 */
389 template <bool B, typename T>
390 struct Case {
391 static constexpr bool value = B;
392 using type = T;
393 };
394 template <typename HEAD, typename... TAIL>
395 struct Select : conditional_t<HEAD::value, HEAD, Select<TAIL...>> {};
396 template <typename T>
397 struct Select<T> {
398 using type = T;
399 };
400 template <bool B, typename T>
401 struct Select<Case<B, T>> {
402 // last one had better be true!
403 static_assert (B, "!");
404 using type = T;
405 };
406 template <typename HEAD, typename... TAIL>
407 using Select_t = typename Select<HEAD, TAIL...>::type;
408
409}
410
411/*
412 ********************************************************************************
413 ***************************** Implementation Details ***************************
414 ********************************************************************************
415 */
416#include "Concepts.inl"
417
418#endif /*_Stroika_Foundation_Common_Concept_h_ */
Concept: trivial shorthand for variadic same_as A or same_as B, or ...
Definition Concepts.h:122
concept true if integral or floating-point type 'T'.
Definition Concepts.h:73
check T has had remove_cvref_t called on it (e.g. ICVRefTd<const string&> is string)
Definition Concepts.h:378
Concept checks if the given type T has a const size() method which can be called to return a size_t.
Definition Concepts.h:288
Concept checks if the given type T has a value_type (type) member.
Definition Concepts.h:341
like std::invocable concept, except also requires the invocation doesn't raise exceptions
Definition Concepts.h:79
return true iff argument type T, is std::pair<a,b> for some a/b types
Definition Concepts.h:182
return true iff argument type T, is std::shared_ptr<A> for some A types
Definition Concepts.h:198
Concept ITuple<T> check if T is a tuple.
Definition Concepts.h:227
detect if T is a std::variant<> type.
Definition Concepts.h:236
handy re-usable concept, with the obvious meaning, and strangely omitted from std-c++ (though used in...
Definition StdCompat.h:150
like convertible_to, but also handling cases where T has an explicit CTOR taking From
Definition StdCompat.h:160
equality_comparable_with, but less strict - just checks if it can be equality compared!
Definition Concepts.h:147
Concept: std::is_trivially_copyable_v.
Definition Concepts.h:128
typename Private_::ExtractValueType< remove_cvref_t< T > >::type ExtractValueType_t
Extract the type of elements in a container, or returned by an iterator (value_type) or void it there...
Definition Concepts.h:372