Stroika Library 3.0d23
 
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 namespace Private_ {
60 struct void_type {
61 using type = void;
62 };
63 }
64
65 /**
66 * \brief ClassNotFinal
67 */
68 template <typename T>
69 concept ClassNotFinal = not is_final_v<T>;
70
71 /**
72 * \brief concept true if integral or floating-point type 'T'. Not sure why not provided by std c++
73 *
74 * Also note - NOT marked true for arithmetic-like types, like big-num package (perhaps provide another concept for this).
75 */
76 template <typename T>
77 concept IBuiltinArithmetic = is_arithmetic_v<T>;
78
79 /**
80 * \brief Extract the number of arguments, return type, and each individual argument type from a lambda or simple function object.
81 *
82 * \par Example Usage
83 * \code
84 * auto lambda = [](int i) { return long(i*10); };
85 *
86 * using traits = FunctionTraits<decltype(lambda)>;
87 *
88 * static_assert (traits::kArity == 1);
89 * static_assert (same_as<long, traits::result_type>);
90 * static_assert (same_as<int, traits::arg<0>::type>);
91 * \endcode
92 *
93 * CREDITS:
94 * From https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda
95 * https://stackoverflow.com/users/224671/kennytm
96 *
97 * For generic types, directly use the result of the signature of its 'operator()'
98 * Specialize for pointers to member function
99 *
100 * \note this doesn't work for function objects that have templated operator() - such as String::EqualsComparer, since there is no type to extract.
101 */
102 template <typename T>
103 struct FunctionTraits : public FunctionTraits<decltype (&T::operator())> {};
104 template <typename CLASS_TYPE, typename RETURN_TYPE, typename... ARGS>
105 struct FunctionTraits<RETURN_TYPE (CLASS_TYPE::*) (ARGS...) const> {
106 /**
107 * \brief Number of arguments
108 */
109 static inline constexpr size_t kArity = sizeof...(ARGS);
110
111 /**
112 * Function return type.
113 */
114 using result_type = RETURN_TYPE;
115
116 /**
117 * type of the ith 'arg';
118 *
119 * \note UNCLEAR if/how this might work if the function is overloaded...
120 *
121 * \see arg_t, ArgOrVoid, ArgOrVoid_t
122 */
123 template <size_t i>
124 struct arg {
125 using type = typename tuple_element<i, tuple<ARGS...>>::type;
126 // the i-th argument is equivalent to the i-th tuple element of a tuple
127 // composed of those arguments.
128 };
129
130 /**
131 */
132 template <size_t i>
133 using arg_t = typename arg<i>::type;
134
135 /**
136 * \brief like 'arg' - except that if index > max legal, instead of failing to compile, will return void. Helpful
137 * sometimes in contexts where c++ templates run more code than you might want.
138 */
139 template <size_t i>
140 struct ArgOrVoid {
141 using type = typename conditional_t<(i < sizeof...(ARGS)), tuple_element<i, tuple<ARGS...>>, Private_::void_type>::type;
142 };
143
144 /**
145 */
146 template <size_t i>
147 using ArgOrVoid_t = typename ArgOrVoid<i>::type;
148 };
149
150 /**
151 * \brief like std::invocable concept, except also requires the invocation doesn't raise exceptions
152 */
153 template <typename F, typename... Args>
154 concept INoThrowInvocable = invocable<F, Args...> and requires (F f, Args... args) {
155 { noexcept (f (args...)) };
156 };
157
158 /**
159 * \par Example Usage
160 * \code
161 * static_assert (invocable_r<decltype ([] (int) { return ""; }), const char*, int>);
162 * static_assert (invocable_r<decltype ([] (char*, char*) {}), void, char*, char*>);
163 * \endcode
164 *
165 * \note used STL-style name since so closely related to invocable - which is part of the standard library.
166 */
167 template <typename F, typename R, typename... Args>
168 concept invocable_r = invocable<F, Args...> && convertible_to<invoke_result_t<F, Args...>, R>;
169 static_assert (invocable_r<decltype ([] (int) { return ""; }), const char*, int>);
170 static_assert (invocable_r<decltype ([] (char*, char*) {}), void, char*, char*>);
171
172 // From https://stackoverflow.com/questions/74383254/concept-that-models-only-the-stdchrono-duration-types
173 template <typename T>
174 concept IDuration =
175 requires { []<typename Rep, typename Period> (type_identity<chrono::duration<Rep, Period>>) {}(type_identity<T> ()); };
176 static_assert (not IDuration<float>);
177
178 // From https://stackoverflow.com/questions/74383254/concept-that-models-only-the-stdchrono-duration-types
179 template <typename T>
180 concept ITimePoint =
181 requires { []<typename CLOCK, typename DURATION> (type_identity<chrono::time_point<CLOCK, DURATION>>) {}(type_identity<T> ()); };
182 static_assert (not ITimePoint<float>);
183
184 /**
185 * \brief concept - trivial shorthand for variadic same_as A or same_as B, or ...
186 *
187 * \par Example Usage
188 * \code
189 * template <typename T>
190 * 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>;
191 *
192 * template <typename T>
193 * concept IBasicUNICODECodePoint = Common::IAnyOf<remove_cv_t<T>, char8_t, char16_t, char32_t>;
194 * \endcode
195 */
196 template <typename T, typename... U>
197 concept IAnyOf = (same_as<T, U> or ...);
198
199 /**
200 * \brief concept version of std::is_trivially_copyable_v
201 */
202 template <typename T>
203 concept trivially_copyable = is_trivially_copyable_v<T>;
204
205 /**
206 * A template which ignores its template arguments, and always returns true_type;
207 * NOT crazy - helpful is template metaprogramming.
208 */
209 template <typename...>
210 using True = true_type;
211
214
215 /**
216 * \brief equality_comparable_with, but less strict - just checks if it can be equality compared!
217 *
218 * INSPIRATION: https://godbolt.org/z/qevGWKan4
219 * static_assert (equality_comparable_with<nullopt_t, optional<int>>); // note this fails
220 */
221 template <class _Ty1, class _Ty2>
222 concept Weak_Equality_Comparable_With = requires (const remove_reference_t<_Ty1>& __x, const remove_reference_t<_Ty2>& __y) {
223 { __x == __y } -> Boolean_testable;
224 { __x != __y } -> Boolean_testable;
225 };
226 static_assert (not equality_comparable_with<nullopt_t, optional<int>>);
228
229 /**
230 */
231 template <typename OT>
232 concept IOptional = same_as<remove_cvref_t<OT>, optional<typename OT::value_type>>;
233 static_assert (IOptional<optional<int>>);
234 static_assert (not IOptional<int>);
235
236 namespace Private_ {
237#if qCompilerAndStdLib_template_concept_matcher_requires_Buggy
238 template <typename T1, typename T2 = void>
239 struct is_shared_ptr_ : false_type {};
240 template <typename T1>
241 struct is_shared_ptr_<shared_ptr<T1>> : true_type {};
242 template <typename T1, typename T2 = void>
243 struct is_pair_ : false_type {};
244 template <typename T1, typename T2>
245 struct is_pair_<pair<T1, T2>> : true_type {};
246 template <typename... ARGS>
247 struct is_variant_ : false_type {};
248 template <typename... ARGS>
249 struct is_variant_<variant<ARGS...>> : true_type {};
250#endif
251 }
252
253 /**
254 * \brief return true iff argument type T, is std::pair<a,b> for some a/b types
255 */
256 template <typename T>
257 concept IPair =
258#if qCompilerAndStdLib_template_concept_matcher_requires_Buggy
259 Private_::is_pair_<T>::value
260#else
261 requires (T t) {
262 {
263 []<typename T1, typename T2> (pair<T1, T2>) {}(t)
264 };
265 }
266#endif
267 ;
268
269 /**
270 * \brief return true iff argument type T, is std::shared_ptr<A> for some A types
271 */
272 template <typename T>
273 concept ISharedPtr =
274#if qCompilerAndStdLib_template_concept_matcher_requires_Buggy
275 Private_::is_shared_ptr_<T>::value
276#else
277
278 requires (T t) {
279 {
280 []<typename T1> (shared_ptr<T1>) {}(t)
281 };
282 }
283#endif
284 ;
285 static_assert (ISharedPtr<shared_ptr<int>>);
286 static_assert (not ISharedPtr<int>);
287
288 namespace Private_ {
289 template <typename T, size_t N>
290 concept has_tuple_element = requires (T t) {
291 typename tuple_element_t<N, remove_const_t<T>>;
292 { get<N> (t) } -> convertible_to<const tuple_element_t<N, T>&>;
293 };
294 }
295
296 /**
297 * \brief Concept ITuple<T> check if T is a tuple.
298 *
299 * based on https://stackoverflow.com/questions/68443804/c20-concept-to-check-tuple-like-types
300 */
301 template <typename T>
302 concept ITuple = !is_reference_v<T> && requires (T t) {
303 typename tuple_size<T>::type;
304 requires derived_from<tuple_size<T>, integral_constant<size_t, tuple_size_v<T>>>;
305 } && []<size_t... N> (index_sequence<N...>) { return (Private_::has_tuple_element<T, N> && ...); }(make_index_sequence<tuple_size_v<T>> ());
306
307 /**
308 * \brief - detect if T is a std::variant<> type.
309 */
310 template <typename T>
311 concept IVariant =
312#if qCompilerAndStdLib_template_concept_matcher_requires_Buggy
313 Private_::is_variant_<T>::value
314#else
315 requires (T t) {
316 {
317 []<typename... TYPES> (variant<TYPES...>) {}(t)
318 };
319 }
320#endif
321 ;
322 static_assert (not IVariant<int>);
323 static_assert (IVariant<variant<int>>);
324
325 /**
326 * Concepts let you construct a 'template' of one arg from one with two args, but class, and variable templates don't allow
327 * this; but this magic trick of double indirection does allow it. And cannot use concepts as template arguments to another template
328 * sadly, so need this trick...
329 *
330 * The 'test' here just invokes convertible_to<TEST_ARGUMENT, T>
331 */
332 template <typename T>
334 template <typename TEST_ARGUMENT>
335 using Test = conditional_t<convertible_to<TEST_ARGUMENT, T>, true_type, false_type>;
336 };
337
338 /**
339 * Concepts let you construct a 'template' of one arg from one with two args, but class, and variable templates don't allow
340 * this; but this magic trick of double indirection does allow it. And cannot use concepts as template arguments to another template
341 * sadly, so need this trick...
342 *
343 * The 'test' here just invokes constructible_from<TEST_ARGUMENT, T>
344 */
345 template <typename T>
347 template <typename TEST_ARGUMENT>
348 using Test = conditional_t<constructible_from<TEST_ARGUMENT, T>, true_type, false_type>;
349 };
350
351 /**
352 * \brief Concept checks if the given type T has a const size() method which can be called to return a size_t.
353 *
354 * \par Example Usage
355 * \code
356 * if constexpr (IHasSizeMethod<T>) {
357 * T a{};
358 * return a.size ();
359 * }
360 * \endcode
361 */
362 template <typename T>
363 concept IHasSizeMethod = requires (const T& t) {
364 { t.size () } -> convertible_to<size_t>;
365 };
366
367 namespace Private_ {
368 template <typename T>
369 concept HasEq_ = requires (T t) {
370 { t == t } -> convertible_to<bool>;
371 };
372 template <typename T>
373 constexpr inline bool HasEq_v_ = HasEq_<T>;
374 template <typename T, typename U>
375 constexpr inline bool HasEq_v_<pair<T, U>> = HasEq_v_<T> and HasEq_v_<U>;
376 template <typename... Ts>
377 constexpr inline bool HasEq_v_<tuple<Ts...>> = (HasEq_v_<Ts> and ...);
378 template <typename T>
379 constexpr bool HasUsableEqualToOptimization ()
380 {
381 // static_assert (Common::IOperatorEq<remove_cvref_t<T>> and ! equality_comparable<T>);
382 // static_assert (not Common::IOperatorEq<T> and equality_comparable<T>);
383 // static_assert (Common::IOperatorEq<remove_cvref_t<T>> == equality_comparable<T>);
384 // @todo figure out why Private_::HasEq_v_ needed and cannot use equality_comparable
385 if constexpr (Private_::HasEq_v_<T>) {
386 struct EqualToEmptyTester_ : equal_to<T> {
387 int a;
388 };
389 // leverage empty base class optimization to see if equal_to contains any real data
390 return sizeof (EqualToEmptyTester_) == sizeof (int);
391 }
392 return false;
393 }
394 }
395
396 /**
397 * Check if equal_to<T> is both well defined, and contains no data. The reason it matters that it contains no data, is because
398 * then one instance is as good as another, and it need not be passed anywhere, opening an optimization opportunity.
399 */
400 template <typename T>
401 concept IEqualToOptimizable = equality_comparable<T> and Private_::HasUsableEqualToOptimization<T> ();
402
403 /**
404 * \brief Concept checks if the given type T has a value_type (type) member
405 *
406 * \par Example Usage
407 * \code
408 * if constexpr (IHasValueType<T>) {
409 * typename T::value_type x;
410 * }
411 * \endcode
412 *
413 * \note this replaces Stroika v2.1 constexpr inline bool has_value_type_v template variable
414 */
415 template <typename T>
416 concept IHasValueType = requires (T t) { typename T::value_type; };
417
418 namespace Private_ {
419 template <typename T, typename = void>
420 struct ExtractValueType {
421 using type = void;
422 };
423 template <IHasValueType T>
424 struct ExtractValueType<T> {
425 using type = typename T::value_type;
426 };
427 template <typename T>
428 struct ExtractValueType<const T*, void> {
429 using type = T;
430 };
431 template <typename T>
432 struct ExtractValueType<T*, void> {
433 using type = T;
434 };
435 }
436
437 /**
438 * \brief Extract the type of elements in a container, or returned by an iterator (value_type) or void it there is no value_type
439 *
440 * \note when known if argument is container or iterator, use std::iter_value_t, or std::ranges::range_value_t
441 *
442 * If the given T has a field value_type, return it; returns void if T has no value_type
443 *
444 * NOTE - similar to std::ranges::range_value_t or std::iter_value_t except works with other types.
445 */
446 template <typename T>
447 using ExtractValueType_t = typename Private_::ExtractValueType<remove_cvref_t<T>>::type;
448
449 /**
450 * @brief check T has had remove_cvref_t called on it (e.g. ICVRefTd<const string&> is string)
451 */
452 template <typename T>
453 concept ICVRefTd = same_as<T, remove_cvref_t<T>>;
454
455 /**
456 * from https://stackoverflow.com/questions/32785105/implementing-a-switch-type-trait-with-stdconditional-t-chain-calls
457 * \par Example Usage
458 * \code
459 * using Type = Select_t<Case<false, void>,
460 * Case<false, int>,
461 * Case<true, std::string>>;
462 * \endcode
463 */
464 template <bool B, typename T>
465 struct Case {
466 static constexpr bool value = B;
467 using type = T;
468 };
469 template <typename HEAD, typename... TAIL>
470 struct Select : conditional_t<HEAD::value, HEAD, Select<TAIL...>> {};
471 template <typename T>
472 struct Select<T> {
473 using type = T;
474 };
475 template <bool B, typename T>
476 struct Select<Case<B, T>> {
477 // last one had better be true!
478 static_assert (B, "!");
479 using type = T;
480 };
481 template <typename HEAD, typename... TAIL>
482 using Select_t = typename Select<HEAD, TAIL...>::type;
483
484}
485
486/*
487 ********************************************************************************
488 ***************************** Implementation Details ***************************
489 ********************************************************************************
490 */
491#include "Concepts.inl"
492
493#endif /*_Stroika_Foundation_Common_Concept_h_ */
concept - trivial shorthand for variadic same_as A or same_as B, or ...
Definition Concepts.h:197
concept true if integral or floating-point type 'T'. Not sure why not provided by std c++
Definition Concepts.h:77
check T has had remove_cvref_t called on it (e.g. ICVRefTd<const string&> is string)
Definition Concepts.h:453
Concept checks if the given type T has a const size() method which can be called to return a size_t.
Definition Concepts.h:363
Concept checks if the given type T has a value_type (type) member.
Definition Concepts.h:416
like std::invocable concept, except also requires the invocation doesn't raise exceptions
Definition Concepts.h:154
return true iff argument type T, is std::pair<a,b> for some a/b types
Definition Concepts.h:257
return true iff argument type T, is std::shared_ptr<A> for some A types
Definition Concepts.h:273
Concept ITuple<T> check if T is a tuple.
Definition Concepts.h:302
detect if T is a std::variant<> type.
Definition Concepts.h:311
handy re-usable concept, with the obvious meaning, and strangely omitted from std-c++ (though used in...
Definition StdCompat.h:92
like convertible_to, but also handling cases where T has an explicit CTOR taking From
Definition StdCompat.h:102
equality_comparable_with, but less strict - just checks if it can be equality compared!
Definition Concepts.h:222
concept version of std::is_trivially_copyable_v
Definition Concepts.h:203
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:447
Extract the number of arguments, return type, and each individual argument type from a lambda or simp...
Definition Concepts.h:103