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