diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fb4d5f9f..e091b46f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,11 +11,18 @@ set(PACKAGE_VERSION ${sqlite_orm_VERSION}) project("sqlite_orm" VERSION ${PACKAGE_VERSION}) # Handling C++ standard version to use +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.25) +option(SQLITE_ORM_ENABLE_CXX_26 "Enable C++ 26" OFF) +endif() option(SQLITE_ORM_ENABLE_CXX_23 "Enable C++ 23" OFF) option(SQLITE_ORM_ENABLE_CXX_20 "Enable C++ 20" OFF) option(SQLITE_ORM_ENABLE_CXX_17 "Enable C++ 17" OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) -if(SQLITE_ORM_ENABLE_CXX_23) + +if(SQLITE_ORM_ENABLE_CXX_26) + set(CMAKE_CXX_STANDARD 26) + message(STATUS "SQLITE_ORM: Build with C++26 features") +elseif(SQLITE_ORM_ENABLE_CXX_23) set(CMAKE_CXX_STANDARD 23) message(STATUS "SQLITE_ORM: Build with C++23 features") elseif(SQLITE_ORM_ENABLE_CXX_20) @@ -25,9 +32,9 @@ elseif(SQLITE_ORM_ENABLE_CXX_17) set(CMAKE_CXX_STANDARD 17) message(STATUS "SQLITE_ORM: Build with C++17 features") else() - # fallback to C++14 if there is no special instruction - set(CMAKE_CXX_STANDARD 14) - message(STATUS "SQLITE_ORM: Build with C++14 features") + # fallback to C++17 if there is no special instruction + set(CMAKE_CXX_STANDARD 17) + message(STATUS "SQLITE_ORM: Build with C++17 features") endif() set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/appveyor.yml b/appveyor.yml index 1f0a07715..c1adb301d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -108,7 +108,7 @@ for: install: - |- cd C:\Tools\vcpkg - git fetch --tags && git checkout 2025.03.19 + git fetch --tags && git checkout 2025.04.09 cd %APPVEYOR_BUILD_FOLDER% C:\Tools\vcpkg\bootstrap-vcpkg.bat -disableMetrics C:\Tools\vcpkg\vcpkg integrate install @@ -142,7 +142,7 @@ for: install: - |- pushd $HOME/vcpkg - git fetch --tags && git checkout 2025.03.19 + git fetch --tags && git checkout 2025.04.09 popd $HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics $HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets @@ -170,7 +170,7 @@ for: # using custom vcpkg triplets for building and linking dynamic dependent libraries install: - |- - git clone --depth 1 --branch 2025.03.19 https://github.com/microsoft/vcpkg.git $HOME/vcpkg + git clone --depth 1 --branch 2025.04.09 https://github.com/microsoft/vcpkg.git $HOME/vcpkg $HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics $HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets vcpkg install sqlite3[core,dbstat,math,json1,fts5,soundex] catch2 --overlay-triplets=vcpkg/triplets diff --git a/dev/arg_values.h b/dev/arg_values.h index 7ad6be04e..eae38760e 100644 --- a/dev/arg_values.h +++ b/dev/arg_values.h @@ -111,9 +111,9 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { mutable arg_value currentValue; }; - arg_values() : arg_values(0, nullptr) {} + arg_values() = default; - arg_values(int argsCount_, sqlite3_value** values_) : argsCount(argsCount_), values(values_) {} + arg_values(int nValues, sqlite3_value** values) : argsCount(nValues), values(values) {} size_t size() const { return this->argsCount; diff --git a/dev/function.h b/dev/function.h index c2b66dd2b..a7660179c 100644 --- a/dev/function.h +++ b/dev/function.h @@ -1,11 +1,12 @@ #pragma once #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::enable_if, std::is_member_function_pointer, std::is_function, std::remove_const, std::decay, std::is_convertible, std::is_same, std::false_type, std::true_type +#include // std::enable_if, std::is_member_function_pointer, std::is_function, std::remove_const, std::decay, std::is_convertible, std::is_same, std::false_type, std::true_type, std::is_pointer #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED #include // std::copy_constructible #endif #include // std::tuple, std::tuple_size, std::tuple_element +#include // std::bind_front #include // std::min, std::copy_n #include // std::move, std::forward #endif @@ -130,7 +131,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template concept orm_quoted_scalar_function = requires(const Q& quotedF) { quotedF.name(); - quotedF.callable(); + quotedF._callable(); }; #endif } @@ -165,6 +166,17 @@ namespace sqlite_orm { struct callable_arguments : callable_arguments_impl {}; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using overloaded_callop_t = decltype(static_cast(&F::operator())); + + template + using overloaded_static_callop_t = +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + decltype(static_cast(&F::operator())); +#else + std::enable_if_t, void>; +#endif + /* * Bundle of type and name of a quoted user-defined function. */ @@ -339,7 +351,7 @@ namespace sqlite_orm { * * Use the variable template `func<>` to instantiate. * - * Calling the function captures the parameters in a `function_call` node. + * Calling the generator captures the parameters in a `function_call` expression. */ template struct function { @@ -347,21 +359,21 @@ namespace sqlite_orm { using callable_type = UDF; /* - * Generates the SQL function call. + * Generates the SQL function call expression. */ template function_call operator()(CallArgs... callArgs) const { check_function_call(); - return {this->udf_holder(), {std::forward(callArgs)...}}; - } - - constexpr auto udf_holder() const { - return internal::udf_holder{}; + return {_udf_holder(), {std::forward(callArgs)...}}; } // returns a character range constexpr auto name() const { - return this->udf_holder()(); + return _udf_holder()(); + } + + constexpr auto _udf_holder() const { + return internal::udf_holder{}; } }; @@ -370,16 +382,13 @@ namespace sqlite_orm { * Generator of a user-defined function call in a sql query expression. * * Use the string literal operator template `""_scalar.quote()` to quote - * a freestanding function, stateless lambda or function object. + * a freestanding function, lambda or function object. * - * Calling the function captures the parameters in a `function_call` node. + * Calling the generator captures the parameters in a `function_call` expression. * - * Internal note: - * 1. Captures and represents a function [pointer or object], especially one without side effects. - * If `F` is a stateless function object, `quoted_scalar_function::callable()` returns the original function object, - * otherwise it is assumed to have possibe side-effects and `quoted_scalar_function::callable()` returns a copy. - * 2. The nested `udf_type` typename is deliberately chosen to be the function signature, - * and will be the abstracted version of the user-defined function. + * Internal notes: + * 1. The nested `udf_type` typename is deliberately chosen to be the function signature, + * and will be the abstracted version of the specified quoted function. */ template struct quoted_scalar_function { @@ -387,42 +396,68 @@ namespace sqlite_orm { using callable_type = F; /* - * Generates the SQL function call. + * Generates the SQL function call expression. */ template function_call operator()(CallArgs... callArgs) const { check_function_call(); - return {this->udf_holder(), {std::forward(callArgs)...}}; + return {_udf_holder(), {std::forward(callArgs)...}}; } - /* - * Return original `udf` if stateless or a copy of it otherwise + constexpr auto name() const { + return _nme; + } + + /* + * Make the final callable that will be used to apply the user-defined function: + * - if `F` is a pointer to a freestanding function, return it as is + * - if `F` is a static call operator, return a pointer to it; + * the function signature is used to pick the function object's static call operator + * - if `F` is a stateless functor, bind a reference to the original function object its call operator; + * the function signature is used to pick the function object's call operator + * - if `F` is a stateful functor, bind a copy of the original function object to its call operator; + * the function signature is used to pick the function object's call operator */ - constexpr decltype(auto) callable() const { - if constexpr (stateless) { - return (this->udf); - } else { + constexpr auto _callable() const { + // function pointer + if constexpr (std::is_pointer_v) { + return _udf; + } + // static call operator + else if constexpr (polyfill::is_detected_v) { + return static_cast(&F::operator()); + } + // stateless functor + else if constexpr (stateless) { +#if __cpp_lib_bind_front >= 202306L // NTTP callables + return std::bind_front(&F::operator())>(std::ref(_udf)); +#else + return std::bind_front(static_cast(&F::operator()), std::ref(_udf)); +#endif + } + // stateful functor + else { // non-const copy - return F(this->udf); +#if __cpp_lib_bind_front >= 202306L // NTTP callables + return std::bind_front(&F::operator())>(_udf); +#else + return std::bind_front(static_cast(&F::operator()), _udf); +#endif } } - constexpr auto udf_holder() const { + constexpr auto _udf_holder() const { return internal::udf_holder{this->name()}; } - constexpr auto name() const { - return this->nme; - } - template consteval quoted_scalar_function(const char (&name)[N], Args&&... constructorArgs) : - udf(std::forward(constructorArgs)...) { - std::copy_n(name, N, this->nme); + _udf(std::forward(constructorArgs)...) { + std::copy_n(name, N, _nme); } - F udf; - char nme[N]; + F _udf; + char _nme[N]; }; template @@ -440,11 +475,10 @@ namespace sqlite_orm { /* * From a classic function object instance. */ - template - requires (orm_classic_function_object && (stateless || std::copy_constructible)) + template + requires (stateless || std::copy_constructible) [[nodiscard]] consteval auto quote(F callable) const { using Sig = function_signature_type_t; - // detect whether overloaded call operator can be picked using `Sig` return quoted_scalar_function{this->cstr, std::move(callable)}; } @@ -452,9 +486,13 @@ namespace sqlite_orm { * From a function object instance, picking the overloaded call operator. */ template - requires ((stateless || std::copy_constructible)) + requires (stateless || std::copy_constructible) [[nodiscard]] consteval auto quote(F callable) const { // detect whether overloaded call operator can be picked using `Sig` + static_assert(polyfill::is_detected_v || + polyfill::is_detected_v, + "No call operator with the specified signature available; have you overlooked the method " + "qualifiers?"); return quoted_scalar_function{this->cstr, std::move(callable)}; } @@ -472,9 +510,13 @@ namespace sqlite_orm { * From a function object type, picking the overloaded call operator. */ template - requires ((stateless || std::copy_constructible)) + requires (stateless || std::copy_constructible) [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { // detect whether overloaded call operator can be picked using `Sig` + static_assert(polyfill::is_detected_v || + polyfill::is_detected_v, + "No call operator with the specified signature available; have you overlooked the method " + "qualifiers?"); return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; } }; @@ -483,7 +525,7 @@ namespace sqlite_orm { } SQLITE_ORM_EXPORT namespace sqlite_orm { - /** @short Call a user-defined function. + /** @short Define a user-defined function. * * Note: Currently the number of call arguments is checked and whether the types of pointer values match, * but other call argument types are not checked against the parameter types of the function. @@ -505,17 +547,17 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES inline namespace literals { - /* @short Create a scalar function from a freestanding function, stateless lambda or function object, + /* @short Create a scalar function from a freestanding function, lambda or function object, * and call such a user-defined function. * * If you need to pick a function or method from an overload set, or pick a template function you can - * specify an explicit function signature in the call to `from()`. + * specify an explicit function signature in the call to `quote()`. * * Examples: * // freestanding function from a library * constexpr orm_quoted_scalar_function auto clamp_int_f = "clamp_int"_scalar.quote(std::clamp); * // stateless lambda - * constexpr orm_quoted_scalar_function auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.quote([](unsigned long errcode) { + * constexpr orm_quoted_scalar_function auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.quote([](unsigned long errcode) static { * return errcode != 0; * }); * // function object instance diff --git a/dev/functional/cxx_core_features.h b/dev/functional/cxx_core_features.h index 08c472021..d2e4f6d24 100644 --- a/dev/functional/cxx_core_features.h +++ b/dev/functional/cxx_core_features.h @@ -65,6 +65,10 @@ #define SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED #endif +#if __cpp_contracts >= 202502L +#define SQLITE_ORM_CONTRACTS_SUPPORTED +#endif + #if __cplusplus >= 202002L #define SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED #define SQLITE_ORM_INITSTMT_RANGE_BASED_FOR_SUPPORTED diff --git a/dev/pragma.h b/dev/pragma.h index 15204c5b8..6a67c4eef 100644 --- a/dev/pragma.h +++ b/dev/pragma.h @@ -171,9 +171,9 @@ namespace sqlite_orm { this->executor.perform_exec( connection.get(), sql, - [](void* data, int argc, orm_gsl::zstring* argv, orm_gsl::zstring*) -> int { + [](void* data, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring*) -> int { auto& res = *(std::vector*)data; - if (argc) { + { auto index = 0; auto cid = atoi(argv[index++]); std::string name = argv[index++]; @@ -212,9 +212,9 @@ namespace sqlite_orm { this->executor.perform_exec( connection.get(), sql, - [](void* data, int argc, orm_gsl::zstring* argv, orm_gsl::zstring*) -> int { + [](void* data, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring*) -> int { auto& res = *(std::vector*)data; - if (argc) { + { auto index = 0; auto cid = atoi(argv[index++]); std::string name = argv[index++]; diff --git a/dev/row_extractor.h b/dev/row_extractor.h index 415e2f1b9..5bd19bdc8 100644 --- a/dev/row_extractor.h +++ b/dev/row_extractor.h @@ -117,6 +117,12 @@ namespace sqlite_orm { row_extractor boxed_value_extractor() { return {}; } + + template + T extract_boxed_value(sqlite3_value* value) { + const auto rowExtractor = boxed_value_extractor(); + return rowExtractor.extract(value); + } } } diff --git a/dev/storage_base.h b/dev/storage_base.h index c352c74b5..b60ede12a 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -181,11 +181,9 @@ namespace sqlite_orm { this->executor.perform_exec( db, sql, - [](void* data, int argc, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { - auto& res = *(bool*)data; - if (argc) { - res = !!atoi(argv[0]); - } + [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { + auto& res = *(bool*)userData; + res = !!atoi(argv[0]); return 0; }, &result); @@ -316,7 +314,7 @@ namespace sqlite_orm { * an instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. * - * T - function class. T must have operator() overload and static name function like this: + * T - function class. T must have a single call operator and static name function like this: * ``` * struct SqrtFunction { * @@ -324,7 +322,7 @@ namespace sqlite_orm { * return std::sqrt(arg); * } * - * static const char *name() { + * static const char* name() { * return "SQRT"; * } * }; @@ -369,18 +367,18 @@ namespace sqlite_orm { * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * If `quotedF` contains a freestanding function, stateless lambda or stateless function object, - * `quoted_scalar_function::callable()` uses the original function object, assuming it is free of side effects; + * `quoted_scalar_function::_callable()` uses the original function object, assuming it is free of side effects; * otherwise, it repeatedly uses a copy of the contained function object, assuming possible side effects. */ template requires (orm_quoted_scalar_function) void create_scalar_function() { - using Sig = auto_udf_type_t<(quotedF)>; - using args_tuple = typename callable_arguments::args_tuple; - using return_type = typename callable_arguments::return_type; - constexpr auto argsCount = std::is_same>::value - ? -1 - : int(std::tuple_size::value); + using signature_type = auto_udf_type_t<(quotedF)>; + using args_tuple = typename callable_arguments::args_tuple; + using return_type = typename callable_arguments::return_type; + constexpr int argsCount = std::is_same>::value + ? -1 + : int(std::tuple_size::value); this->scalarFunctions.emplace_back( std::string{quotedF.name()}, argsCount, @@ -389,10 +387,10 @@ namespace sqlite_orm { /* destroy = */ nullptr, /* call = */ - [](sqlite3_context* context, int argsCount, sqlite3_value** values) { - proxy_assert_args_count(context, argsCount); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); - auto result = polyfill::apply(quotedF.callable(), std::move(argsTuple)); + [](sqlite3_context* context, int nValues, sqlite3_value** values) { + proxy_assert_args_count(context, nValues); + args_tuple argsTuple = tuple_from_values{}(values, nValues); + auto result = polyfill::apply(quotedF._callable(), std::move(argsTuple)); statement_binder().result(context, result); }, /* finalCall = */ @@ -765,9 +763,9 @@ namespace sqlite_orm { this->executor.perform_exec( connection.get(), ss.str(), - [](void* data, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*columnName*/) -> int { - auto& objectNames_ = *(data_t*)data; - objectNames_.emplace_back(argv[0]); + [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*columnName*/) -> int { + auto& objectNames = *(data_t*)userData; + objectNames.emplace_back(argv[0]); return 0; }, &objectNames); @@ -841,9 +839,9 @@ namespace sqlite_orm { void create_scalar_function_impl(udf_holder udfName, std::function constructAt) { using args_tuple = typename callable_arguments::args_tuple; using return_type = typename callable_arguments::return_type; - constexpr auto argsCount = std::is_same>::value - ? -1 - : int(std::tuple_size::value); + constexpr int argsCount = std::is_same>::value + ? -1 + : int(std::tuple_size::value); using is_stateless = std::is_empty; auto udfMemorySpace = preallocate_udf_memory(); if constexpr (is_stateless::value) { @@ -856,9 +854,9 @@ namespace sqlite_orm { /* destroy = */ obtain_xdestroy_for(udf_destruct_only_deleter{}), /* call = */ - [](sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto udfPointer = proxy_get_scalar_udf(is_stateless{}, context, argsCount); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); + [](sqlite3_context* context, int nValues, sqlite3_value** values) { + auto udfPointer = proxy_get_scalar_udf(is_stateless{}, context, nValues); + args_tuple argsTuple = tuple_from_values{}(values, nValues); auto result = polyfill::apply(*udfPointer, std::move(argsTuple)); statement_binder().result(context, result); }, @@ -875,9 +873,9 @@ namespace sqlite_orm { std::function constructAt) { using args_tuple = typename callable_arguments::args_tuple; using return_type = typename callable_arguments::return_type; - constexpr auto argsCount = std::is_same>::value - ? -1 - : int(std::tuple_size::value); + constexpr int argsCount = std::is_same>::value + ? -1 + : int(std::tuple_size::value); this->aggregateFunctions.emplace_back( udfName(), argsCount, @@ -885,15 +883,15 @@ namespace sqlite_orm { /* destroy = */ obtain_xdestroy_for(udf_destruct_only_deleter{}), /* step = */ - [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + [](sqlite3_context* context, int nValues, sqlite3_value** values) { F* udfPointer; try { - udfPointer = proxy_get_aggregate_step_udf(context, argsCount); + udfPointer = proxy_get_aggregate_step_udf(context, nValues); } catch (const std::bad_alloc&) { sqlite3_result_error_nomem(context); return; } - args_tuple argsTuple = tuple_from_values{}(values, argsCount); + args_tuple argsTuple = tuple_from_values{}(values, nValues); #if __cpp_lib_bind_front >= 201907L std::apply(std::bind_front(&F::step, udfPointer), std::move(argsTuple)); #else diff --git a/dev/util.h b/dev/util.h index 4bb9fc068..7e727f352 100644 --- a/dev/util.h +++ b/dev/util.h @@ -93,7 +93,7 @@ namespace sqlite_orm { inline void perform_exec(sqlite3* db, orm_gsl::czstring sql, - int (*callback)(void* data, int argc, orm_gsl::zstring* argv, orm_gsl::zstring*), + int (*callback)(void*, int, orm_gsl::zstring*, orm_gsl::zstring*), void* user_data) const { if (this->will_run_query) { this->will_run_query(sql); @@ -109,7 +109,7 @@ namespace sqlite_orm { inline void perform_exec(sqlite3* db, const std::string& query, - int (*callback)(void* data, int argc, orm_gsl::zstring* argv, orm_gsl::zstring*), + int (*callback)(void*, int, orm_gsl::zstring*, orm_gsl::zstring*), void* user_data) const { return perform_exec(db, query.c_str(), callback, user_data); } diff --git a/dev/values_to_tuple.h b/dev/values_to_tuple.h index 4ea2e72dd..77946b373 100644 --- a/dev/values_to_tuple.h +++ b/dev/values_to_tuple.h @@ -2,43 +2,40 @@ #include #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::enable_if, std::is_same, std::index_sequence, std::make_index_sequence +#include // std::index_sequence, std::make_index_sequence #include // std::tuple, std::tuple_size, std::tuple_element #endif -#include "functional/cxx_functional_polyfill.h" -#include "type_traits.h" #include "row_extractor.h" #include "arg_values.h" -namespace sqlite_orm { +namespace sqlite_orm::internal { - namespace internal { - - template - struct tuple_from_values { - template> = true> - SQLITE_ORM_STATIC_CALLOP R operator()(sqlite3_value** values, - int /*argsCount*/) SQLITE_ORM_OR_CONST_CALLOP { - return tuple_from_values::create_from(values, std::make_index_sequence::value>{}); - } - - template> = true> - SQLITE_ORM_STATIC_CALLOP R operator()(sqlite3_value** values, int argsCount) SQLITE_ORM_OR_CONST_CALLOP { - return {arg_values(argsCount, values)}; - } - - private: - template - static Tpl create_from(sqlite3_value** values, std::index_sequence) { - return {tuple_from_values::extract>(values[Idx])...}; - } - - template - static T extract(sqlite3_value* value) { - const auto rowExtractor = boxed_value_extractor(); - return rowExtractor.extract(value); - } - }; - } + template + struct tuple_from_values { + SQLITE_ORM_STATIC_CALLOP Tpl operator()(sqlite3_value** values, + [[maybe_unused]] int nValues) SQLITE_ORM_OR_CONST_CALLOP { +#ifdef SQLITE_ORM_CONTRACTS_SUPPORTED + contract_assert(nValues == std::tuple_size::value); +#endif + return tuple_from_values::create_from(values, std::make_index_sequence::value>{}); + } + + private: + template + static Tpl create_from(sqlite3_value** values, std::index_sequence) { + return {extract_boxed_value>(values[Idx])...}; + } + }; + + /* + * Explicit specialization for `arg_values`. + */ + template<> + struct tuple_from_values> { + SQLITE_ORM_STATIC_CALLOP std::tuple operator()(sqlite3_value** values, + int nValues) SQLITE_ORM_OR_CONST_CALLOP { + return {arg_values(nValues, values)}; + } + }; } diff --git a/examples/user_defined_functions.cpp b/examples/user_defined_functions.cpp index 062d809bb..7040e2331 100644 --- a/examples/user_defined_functions.cpp +++ b/examples/user_defined_functions.cpp @@ -2,6 +2,12 @@ * This example was taken from here http://souptonuts.sourceforge.net/readme_sqlite_tutorial.html */ #include +#include +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#if __cpp_lib_bit_cast >= 201806L +#include +#endif +#endif #include using namespace sqlite_orm; @@ -38,6 +44,18 @@ struct SignFunction { inline constexpr orm_scalar_function auto sign = func; #endif +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES +#if __cpp_lib_bit_cast >= 201806L +/** + * A scalar application-defined function that quotes a stateless lambda. + * Quoting functions is a neat shortcut to avoid defining a class. + */ +inline constexpr orm_quoted_scalar_function auto sign_of = "SIGN_OF"_scalar.quote([](int value) -> int { + return std::bit_cast(value <=> 0); +}); +#endif +#endif + /** * Aggregate function must be defined as a dedicated class with at least three functions: * 1) `void step(...)` which can be called 0 or more times during one call inside single SQL query (once per row). @@ -135,9 +153,20 @@ int main() { */ storage.create_scalar_function(); - // SELECT SIGN(3) - auto signRows = storage.select(sign(3)); - cout << "SELECT SIGN(3) = " << signRows.at(0) << endl; + // SELECT SIGN(3), SIGN(0), SIGN(-3) + { + auto [c1, c2, c3] = storage.select(columns(sign(3), sign(0), sign(-3))).at(0); + cout << "SELECT SIGN(3) = " << c1 << ", SIGN(0) = " << c2 << ", SIGN(-3) = " << c3 << endl; + } + +#if __cpp_lib_bit_cast >= 201806L + storage.create_scalar_function(); + // SELECT SIGN_OF(3), SIGN_OF(0), SIGN_OF(-3) + { + auto [c1, c2, c3] = storage.select(columns(sign_of(3), sign_of(0), sign_of(-3))).at(0); + cout << "SELECT SIGN_OF(3) = " << c1 << ", SIGN_OF(0) = " << c2 << ", SIGN_OF(-3) = " << c3 << endl; + } +#endif storage.insert(Table{1, -1, 2}); storage.insert(Table{2, -2, 4}); @@ -148,13 +177,12 @@ int main() { // SELECT ASUM(a), ASUM(b), ASUM(c) // FROM t - auto aSumRows = - storage.select(columns(accelerated_sum(&Table::a), accelerated_sum(&Table::b), accelerated_sum(&Table::c))); cout << "SELECT ASUM(a), ASUM(b), ASUM(c) FROM t:" << endl; - for (auto& row: aSumRows) { - cout << '\t' << get<0>(row) << endl; - cout << '\t' << get<1>(row) << endl; - cout << '\t' << get<2>(row) << endl; + for (auto [a, b, c]: storage.iterate( + select(columns(accelerated_sum(&Table::a), accelerated_sum(&Table::b), accelerated_sum(&Table::c))))) { + cout << '\t' << a << endl; + cout << '\t' << b << endl; + cout << '\t' << c << endl; } storage.create_scalar_function(); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index eba7cffe2..b14df85aa 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -119,6 +119,10 @@ using std::nullptr_t; #define SQLITE_ORM_STRUCTURED_BINDING_PACK_SUPPORTED #endif +#if __cpp_contracts >= 202502L +#define SQLITE_ORM_CONTRACTS_SUPPORTED +#endif + #if __cplusplus >= 202002L #define SQLITE_ORM_DEFAULT_COMPARISONS_SUPPORTED #define SQLITE_ORM_INITSTMT_RANGE_BASED_FOR_SUPPORTED @@ -10985,11 +10989,12 @@ namespace sqlite_orm { // #include "function.h" #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::enable_if, std::is_member_function_pointer, std::is_function, std::remove_const, std::decay, std::is_convertible, std::is_same, std::false_type, std::true_type +#include // std::enable_if, std::is_member_function_pointer, std::is_function, std::remove_const, std::decay, std::is_convertible, std::is_same, std::false_type, std::true_type, std::is_pointer #ifdef SQLITE_ORM_CPP20_CONCEPTS_SUPPORTED #include // std::copy_constructible #endif #include // std::tuple, std::tuple_size, std::tuple_element +#include // std::bind_front #include // std::min, std::copy_n #include // std::move, std::forward #endif @@ -11200,7 +11205,7 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { template concept orm_quoted_scalar_function = requires(const Q& quotedF) { quotedF.name(); - quotedF.callable(); + quotedF._callable(); }; #endif } @@ -11235,6 +11240,17 @@ namespace sqlite_orm { struct callable_arguments : callable_arguments_impl {}; #ifdef SQLITE_ORM_WITH_CPP20_ALIASES + template + using overloaded_callop_t = decltype(static_cast(&F::operator())); + + template + using overloaded_static_callop_t = +#ifdef SQLITE_ORM_WITH_CPP20_ALIASES + decltype(static_cast(&F::operator())); +#else + std::enable_if_t, void>; +#endif + /* * Bundle of type and name of a quoted user-defined function. */ @@ -11409,7 +11425,7 @@ namespace sqlite_orm { * * Use the variable template `func<>` to instantiate. * - * Calling the function captures the parameters in a `function_call` node. + * Calling the generator captures the parameters in a `function_call` expression. */ template struct function { @@ -11417,21 +11433,21 @@ namespace sqlite_orm { using callable_type = UDF; /* - * Generates the SQL function call. + * Generates the SQL function call expression. */ template function_call operator()(CallArgs... callArgs) const { check_function_call(); - return {this->udf_holder(), {std::forward(callArgs)...}}; - } - - constexpr auto udf_holder() const { - return internal::udf_holder{}; + return {_udf_holder(), {std::forward(callArgs)...}}; } // returns a character range constexpr auto name() const { - return this->udf_holder()(); + return _udf_holder()(); + } + + constexpr auto _udf_holder() const { + return internal::udf_holder{}; } }; @@ -11440,16 +11456,13 @@ namespace sqlite_orm { * Generator of a user-defined function call in a sql query expression. * * Use the string literal operator template `""_scalar.quote()` to quote - * a freestanding function, stateless lambda or function object. + * a freestanding function, lambda or function object. * - * Calling the function captures the parameters in a `function_call` node. + * Calling the generator captures the parameters in a `function_call` expression. * - * Internal note: - * 1. Captures and represents a function [pointer or object], especially one without side effects. - * If `F` is a stateless function object, `quoted_scalar_function::callable()` returns the original function object, - * otherwise it is assumed to have possibe side-effects and `quoted_scalar_function::callable()` returns a copy. - * 2. The nested `udf_type` typename is deliberately chosen to be the function signature, - * and will be the abstracted version of the user-defined function. + * Internal notes: + * 1. The nested `udf_type` typename is deliberately chosen to be the function signature, + * and will be the abstracted version of the specified quoted function. */ template struct quoted_scalar_function { @@ -11457,42 +11470,68 @@ namespace sqlite_orm { using callable_type = F; /* - * Generates the SQL function call. + * Generates the SQL function call expression. */ template function_call operator()(CallArgs... callArgs) const { check_function_call(); - return {this->udf_holder(), {std::forward(callArgs)...}}; + return {_udf_holder(), {std::forward(callArgs)...}}; } - /* - * Return original `udf` if stateless or a copy of it otherwise + constexpr auto name() const { + return _nme; + } + + /* + * Make the final callable that will be used to apply the user-defined function: + * - if `F` is a pointer to a freestanding function, return it as is + * - if `F` is a static call operator, return a pointer to it; + * the function signature is used to pick the function object's static call operator + * - if `F` is a stateless functor, bind a reference to the original function object its call operator; + * the function signature is used to pick the function object's call operator + * - if `F` is a stateful functor, bind a copy of the original function object to its call operator; + * the function signature is used to pick the function object's call operator */ - constexpr decltype(auto) callable() const { - if constexpr (stateless) { - return (this->udf); - } else { + constexpr auto _callable() const { + // function pointer + if constexpr (std::is_pointer_v) { + return _udf; + } + // static call operator + else if constexpr (polyfill::is_detected_v) { + return static_cast(&F::operator()); + } + // stateless functor + else if constexpr (stateless) { +#if __cpp_lib_bind_front >= 202306L // NTTP callables + return std::bind_front(&F::operator())>(std::ref(_udf)); +#else + return std::bind_front(static_cast(&F::operator()), std::ref(_udf)); +#endif + } + // stateful functor + else { // non-const copy - return F(this->udf); +#if __cpp_lib_bind_front >= 202306L // NTTP callables + return std::bind_front(&F::operator())>(_udf); +#else + return std::bind_front(static_cast(&F::operator()), _udf); +#endif } } - constexpr auto udf_holder() const { + constexpr auto _udf_holder() const { return internal::udf_holder{this->name()}; } - constexpr auto name() const { - return this->nme; - } - template consteval quoted_scalar_function(const char (&name)[N], Args&&... constructorArgs) : - udf(std::forward(constructorArgs)...) { - std::copy_n(name, N, this->nme); + _udf(std::forward(constructorArgs)...) { + std::copy_n(name, N, _nme); } - F udf; - char nme[N]; + F _udf; + char _nme[N]; }; template @@ -11510,11 +11549,10 @@ namespace sqlite_orm { /* * From a classic function object instance. */ - template - requires (orm_classic_function_object && (stateless || std::copy_constructible)) + template + requires (stateless || std::copy_constructible) [[nodiscard]] consteval auto quote(F callable) const { using Sig = function_signature_type_t; - // detect whether overloaded call operator can be picked using `Sig` return quoted_scalar_function{this->cstr, std::move(callable)}; } @@ -11522,9 +11560,13 @@ namespace sqlite_orm { * From a function object instance, picking the overloaded call operator. */ template - requires ((stateless || std::copy_constructible)) + requires (stateless || std::copy_constructible) [[nodiscard]] consteval auto quote(F callable) const { // detect whether overloaded call operator can be picked using `Sig` + static_assert(polyfill::is_detected_v || + polyfill::is_detected_v, + "No call operator with the specified signature available; have you overlooked the method " + "qualifiers?"); return quoted_scalar_function{this->cstr, std::move(callable)}; } @@ -11542,9 +11584,13 @@ namespace sqlite_orm { * From a function object type, picking the overloaded call operator. */ template - requires ((stateless || std::copy_constructible)) + requires (stateless || std::copy_constructible) [[nodiscard]] consteval auto quote(Args&&... constructorArgs) const { // detect whether overloaded call operator can be picked using `Sig` + static_assert(polyfill::is_detected_v || + polyfill::is_detected_v, + "No call operator with the specified signature available; have you overlooked the method " + "qualifiers?"); return quoted_scalar_function{this->cstr, std::forward(constructorArgs)...}; } }; @@ -11553,7 +11599,7 @@ namespace sqlite_orm { } SQLITE_ORM_EXPORT namespace sqlite_orm { - /** @short Call a user-defined function. + /** @short Define a user-defined function. * * Note: Currently the number of call arguments is checked and whether the types of pointer values match, * but other call argument types are not checked against the parameter types of the function. @@ -11575,17 +11621,17 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { #ifdef SQLITE_ORM_WITH_CPP20_ALIASES inline namespace literals { - /* @short Create a scalar function from a freestanding function, stateless lambda or function object, + /* @short Create a scalar function from a freestanding function, lambda or function object, * and call such a user-defined function. * * If you need to pick a function or method from an overload set, or pick a template function you can - * specify an explicit function signature in the call to `from()`. + * specify an explicit function signature in the call to `quote()`. * * Examples: * // freestanding function from a library * constexpr orm_quoted_scalar_function auto clamp_int_f = "clamp_int"_scalar.quote(std::clamp); * // stateless lambda - * constexpr orm_quoted_scalar_function auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.quote([](unsigned long errcode) { + * constexpr orm_quoted_scalar_function auto is_fatal_error_f = "IS_FATAL_ERROR"_scalar.quote([](unsigned long errcode) static { * return errcode != 0; * }); * // function object instance @@ -13178,6 +13224,12 @@ namespace sqlite_orm { row_extractor boxed_value_extractor() { return {}; } + + template + T extract_boxed_value(sqlite3_value* value) { + const auto rowExtractor = boxed_value_extractor(); + return rowExtractor.extract(value); + } } } @@ -13827,7 +13879,7 @@ namespace sqlite_orm { inline void perform_exec(sqlite3* db, orm_gsl::czstring sql, - int (*callback)(void* data, int argc, orm_gsl::zstring* argv, orm_gsl::zstring*), + int (*callback)(void*, int, orm_gsl::zstring*, orm_gsl::zstring*), void* user_data) const { if (this->will_run_query) { this->will_run_query(sql); @@ -13843,7 +13895,7 @@ namespace sqlite_orm { inline void perform_exec(sqlite3* db, const std::string& query, - int (*callback)(void* data, int argc, orm_gsl::zstring* argv, orm_gsl::zstring*), + int (*callback)(void*, int, orm_gsl::zstring*, orm_gsl::zstring*), void* user_data) const { return perform_exec(db, query.c_str(), callback, user_data); } @@ -17219,9 +17271,9 @@ namespace sqlite_orm { this->executor.perform_exec( connection.get(), sql, - [](void* data, int argc, orm_gsl::zstring* argv, orm_gsl::zstring*) -> int { + [](void* data, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring*) -> int { auto& res = *(std::vector*)data; - if (argc) { + { auto index = 0; auto cid = atoi(argv[index++]); std::string name = argv[index++]; @@ -17260,9 +17312,9 @@ namespace sqlite_orm { this->executor.perform_exec( connection.get(), sql, - [](void* data, int argc, orm_gsl::zstring* argv, orm_gsl::zstring*) -> int { + [](void* data, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring*) -> int { auto& res = *(std::vector*)data; - if (argc) { + { auto index = 0; auto cid = atoi(argv[index++]); std::string name = argv[index++]; @@ -17643,14 +17695,10 @@ namespace sqlite_orm { #include #ifndef SQLITE_ORM_IMPORT_STD_MODULE -#include // std::enable_if, std::is_same, std::index_sequence, std::make_index_sequence +#include // std::index_sequence, std::make_index_sequence #include // std::tuple, std::tuple_size, std::tuple_element #endif -// #include "functional/cxx_functional_polyfill.h" - -// #include "type_traits.h" - // #include "row_extractor.h" // #include "arg_values.h" @@ -17767,9 +17815,9 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { mutable arg_value currentValue; }; - arg_values() : arg_values(0, nullptr) {} + arg_values() = default; - arg_values(int argsCount_, sqlite3_value** values_) : argsCount(argsCount_), values(values_) {} + arg_values(int nValues, sqlite3_value** values) : argsCount(nValues), values(values) {} size_t size() const { return this->argsCount; @@ -17806,36 +17854,35 @@ SQLITE_ORM_EXPORT namespace sqlite_orm { }; } -namespace sqlite_orm { - - namespace internal { - - template - struct tuple_from_values { - template> = true> - SQLITE_ORM_STATIC_CALLOP R operator()(sqlite3_value** values, - int /*argsCount*/) SQLITE_ORM_OR_CONST_CALLOP { - return tuple_from_values::create_from(values, std::make_index_sequence::value>{}); - } +namespace sqlite_orm::internal { - template> = true> - SQLITE_ORM_STATIC_CALLOP R operator()(sqlite3_value** values, int argsCount) SQLITE_ORM_OR_CONST_CALLOP { - return {arg_values(argsCount, values)}; - } + template + struct tuple_from_values { + SQLITE_ORM_STATIC_CALLOP Tpl operator()(sqlite3_value** values, + [[maybe_unused]] int nValues) SQLITE_ORM_OR_CONST_CALLOP { +#ifdef SQLITE_ORM_CONTRACTS_SUPPORTED + contract_assert(nValues == std::tuple_size::value); +#endif + return tuple_from_values::create_from(values, std::make_index_sequence::value>{}); + } - private: - template - static Tpl create_from(sqlite3_value** values, std::index_sequence) { - return {tuple_from_values::extract>(values[Idx])...}; - } + private: + template + static Tpl create_from(sqlite3_value** values, std::index_sequence) { + return {extract_boxed_value>(values[Idx])...}; + } + }; - template - static T extract(sqlite3_value* value) { - const auto rowExtractor = boxed_value_extractor(); - return rowExtractor.extract(value); - } - }; - } + /* + * Explicit specialization for `arg_values`. + */ + template<> + struct tuple_from_values> { + SQLITE_ORM_STATIC_CALLOP std::tuple operator()(sqlite3_value** values, + int nValues) SQLITE_ORM_OR_CONST_CALLOP { + return {arg_values(nValues, values)}; + } + }; } // #include "arg_values.h" @@ -18226,11 +18273,9 @@ namespace sqlite_orm { this->executor.perform_exec( db, sql, - [](void* data, int argc, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { - auto& res = *(bool*)data; - if (argc) { - res = !!atoi(argv[0]); - } + [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*azColName*/) -> int { + auto& res = *(bool*)userData; + res = !!atoi(argv[0]); return 0; }, &result); @@ -18361,7 +18406,7 @@ namespace sqlite_orm { * an instance of the function object is repeatedly recreated for each result row, * ensuring that the calculations always start with freshly initialized values. * - * T - function class. T must have operator() overload and static name function like this: + * T - function class. T must have a single call operator and static name function like this: * ``` * struct SqrtFunction { * @@ -18369,7 +18414,7 @@ namespace sqlite_orm { * return std::sqrt(arg); * } * - * static const char *name() { + * static const char* name() { * return "SQRT"; * } * }; @@ -18414,18 +18459,18 @@ namespace sqlite_orm { * Can be called at any time (in a single-threaded context) no matter whether the database connection is opened or not. * * If `quotedF` contains a freestanding function, stateless lambda or stateless function object, - * `quoted_scalar_function::callable()` uses the original function object, assuming it is free of side effects; + * `quoted_scalar_function::_callable()` uses the original function object, assuming it is free of side effects; * otherwise, it repeatedly uses a copy of the contained function object, assuming possible side effects. */ template requires (orm_quoted_scalar_function) void create_scalar_function() { - using Sig = auto_udf_type_t<(quotedF)>; - using args_tuple = typename callable_arguments::args_tuple; - using return_type = typename callable_arguments::return_type; - constexpr auto argsCount = std::is_same>::value - ? -1 - : int(std::tuple_size::value); + using signature_type = auto_udf_type_t<(quotedF)>; + using args_tuple = typename callable_arguments::args_tuple; + using return_type = typename callable_arguments::return_type; + constexpr int argsCount = std::is_same>::value + ? -1 + : int(std::tuple_size::value); this->scalarFunctions.emplace_back( std::string{quotedF.name()}, argsCount, @@ -18434,10 +18479,10 @@ namespace sqlite_orm { /* destroy = */ nullptr, /* call = */ - [](sqlite3_context* context, int argsCount, sqlite3_value** values) { - proxy_assert_args_count(context, argsCount); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); - auto result = polyfill::apply(quotedF.callable(), std::move(argsTuple)); + [](sqlite3_context* context, int nValues, sqlite3_value** values) { + proxy_assert_args_count(context, nValues); + args_tuple argsTuple = tuple_from_values{}(values, nValues); + auto result = polyfill::apply(quotedF._callable(), std::move(argsTuple)); statement_binder().result(context, result); }, /* finalCall = */ @@ -18810,9 +18855,9 @@ namespace sqlite_orm { this->executor.perform_exec( connection.get(), ss.str(), - [](void* data, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*columnName*/) -> int { - auto& objectNames_ = *(data_t*)data; - objectNames_.emplace_back(argv[0]); + [](void* userData, int /*argc*/, orm_gsl::zstring* argv, orm_gsl::zstring* /*columnName*/) -> int { + auto& objectNames = *(data_t*)userData; + objectNames.emplace_back(argv[0]); return 0; }, &objectNames); @@ -18886,9 +18931,9 @@ namespace sqlite_orm { void create_scalar_function_impl(udf_holder udfName, std::function constructAt) { using args_tuple = typename callable_arguments::args_tuple; using return_type = typename callable_arguments::return_type; - constexpr auto argsCount = std::is_same>::value - ? -1 - : int(std::tuple_size::value); + constexpr int argsCount = std::is_same>::value + ? -1 + : int(std::tuple_size::value); using is_stateless = std::is_empty; auto udfMemorySpace = preallocate_udf_memory(); if constexpr (is_stateless::value) { @@ -18901,9 +18946,9 @@ namespace sqlite_orm { /* destroy = */ obtain_xdestroy_for(udf_destruct_only_deleter{}), /* call = */ - [](sqlite3_context* context, int argsCount, sqlite3_value** values) { - auto udfPointer = proxy_get_scalar_udf(is_stateless{}, context, argsCount); - args_tuple argsTuple = tuple_from_values{}(values, argsCount); + [](sqlite3_context* context, int nValues, sqlite3_value** values) { + auto udfPointer = proxy_get_scalar_udf(is_stateless{}, context, nValues); + args_tuple argsTuple = tuple_from_values{}(values, nValues); auto result = polyfill::apply(*udfPointer, std::move(argsTuple)); statement_binder().result(context, result); }, @@ -18920,9 +18965,9 @@ namespace sqlite_orm { std::function constructAt) { using args_tuple = typename callable_arguments::args_tuple; using return_type = typename callable_arguments::return_type; - constexpr auto argsCount = std::is_same>::value - ? -1 - : int(std::tuple_size::value); + constexpr int argsCount = std::is_same>::value + ? -1 + : int(std::tuple_size::value); this->aggregateFunctions.emplace_back( udfName(), argsCount, @@ -18930,15 +18975,15 @@ namespace sqlite_orm { /* destroy = */ obtain_xdestroy_for(udf_destruct_only_deleter{}), /* step = */ - [](sqlite3_context* context, int argsCount, sqlite3_value** values) { + [](sqlite3_context* context, int nValues, sqlite3_value** values) { F* udfPointer; try { - udfPointer = proxy_get_aggregate_step_udf(context, argsCount); + udfPointer = proxy_get_aggregate_step_udf(context, nValues); } catch (const std::bad_alloc&) { sqlite3_result_error_nomem(context); return; } - args_tuple argsTuple = tuple_from_values{}(values, argsCount); + args_tuple argsTuple = tuple_from_values{}(values, nValues); #if __cpp_lib_bind_front >= 201907L std::apply(std::bind_front(&F::step, udfPointer), std::move(argsTuple)); #else diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fc6fb4cac..5ea3022ed 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,7 +15,7 @@ include(ucm) # However, Visual C++ has an option that instructs MSBuild to automatically provision the standard modules, # and this option is ON by default when using the `/std:c++latest` compiler switch: # "Build ISO C++23 Standard Library Modules", MSBuild property `BuildStlModules`. -if(SQLITE_ORM_ENABLE_CXX_23 AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.28.2 AND MSVC AND MSVC_VERSION GREATER_EQUAL 1930) +if(CMAKE_CXX_STANDARD GREATER_EQUAL 23 AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.28.2 AND MSVC AND MSVC_VERSION GREATER_EQUAL 1930) add_executable(module_tests named_module/named_module.cpp ) @@ -55,25 +55,16 @@ if(SQLITE_ORM_OMITS_CODECVT) target_compile_definitions(unit_tests PRIVATE SQLITE_ORM_OMITS_CODECVT=1) endif() -if (MSVC) +if(MSVC) target_compile_options(unit_tests PUBLIC # multi-processor compilation - /MP) - if (MSVC_VERSION LESS_EQUAL 1900) - target_compile_options(unit_tests PUBLIC - # C4503: decorated name length exceeded - /wd4503 - # C4800: forcing value to bool (performance warning) - /wd4800) - else() - target_compile_options(unit_tests PUBLIC - # warning-level 4 - /W4 - # C4456: declaration of 'symbol' hides previous local declaration - /wd4456 - # C4458: declaration of 'symbol' hides class member - /wd4458) - endif() + /MP + # warning-level 4 + /W4 + # C4456: declaration of 'symbol' hides previous local declaration + /wd4456 + # C4458: declaration of 'symbol' hides class member + /wd4458) if (CMAKE_CXX_FLAGS MATCHES "/D_UNICODE") # explicitly set the entry point of the executable file, # otherwise for some reason the linker will not pick up `wmain`, which is provided by the static Catch2 library diff --git a/tests/static_tests/function_static_tests.cpp b/tests/static_tests/function_static_tests.cpp index ea27d0ad6..69cd35a86 100644 --- a/tests/static_tests/function_static_tests.cpp +++ b/tests/static_tests/function_static_tests.cpp @@ -243,6 +243,14 @@ TEST_CASE("function static") { } int operator()(int) const; }; +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + struct SFunction2 { + static const char* name() { + return ""; + } + static int operator()(int); + }; +#endif struct AFunction { static const char* name() { return ""; @@ -262,6 +270,15 @@ TEST_CASE("function static") { STATIC_REQUIRE(std::is_same::callable_type, SFunction>::value); STATIC_REQUIRE(std::is_same::udf_type, SFunction>::value); +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + constexpr auto scalar2 = func; + + STATIC_REQUIRE(std::is_same>::value); + STATIC_REQUIRE(std::is_same>::value); + STATIC_REQUIRE(std::is_same::callable_type, SFunction2>::value); + STATIC_REQUIRE(std::is_same::udf_type, SFunction2>::value); +#endif + #ifdef SQLITE_ORM_WITH_CPP20_ALIASES STATIC_REQUIRE(orm_scalar_function); STATIC_REQUIRE_FALSE(orm_aggregate_function); @@ -273,34 +290,266 @@ TEST_CASE("function static") { STATIC_REQUIRE_FALSE(storage_aggregate_callable); STATIC_REQUIRE(storage_aggregate_callable); STATIC_REQUIRE_FALSE(storage_scalar_callable); + +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + STATIC_REQUIRE(orm_scalar_function); + STATIC_REQUIRE_FALSE(orm_aggregate_function); + + STATIC_REQUIRE(storage_scalar_callable); + STATIC_REQUIRE_FALSE(storage_aggregate_callable); +#endif #endif } #ifdef SQLITE_ORM_WITH_CPP20_ALIASES SECTION("quoted") { - constexpr auto quotedScalar = "f"_scalar.quote(std::clamp); - using quoted_type = decltype("f"_scalar.quote(std::clamp)); + struct functor { + bool operator()(int&, int&) const = delete; + + bool operator()(const int&, const int&) const { + return true; + } + +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + static bool operator()(int, int) { + return true; + } +#endif + }; +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + // note: this static lambda lives up here because of GCC 13.2 and mangling issues + constexpr auto lambda_static_dummy = [](unsigned long errcode) static { + return errcode != 0; + }; +#endif + + SECTION("detect overloaded call operator") { + constexpr auto lambda = [](unsigned long) {}; + using lambda_type = std::remove_const_t; - STATIC_REQUIRE(quotedScalar.nme[0] == 'f' && quotedScalar.nme[1] == '\0'); - STATIC_REQUIRE(std::is_same_v)*, - const int&(const int&, const int&, const int&), - 2>>); + STATIC_REQUIRE( + polyfill::is_detected_v); + STATIC_REQUIRE_FALSE( + polyfill::is_detected_v); + } +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + SECTION("detect overloaded static call operator") { + constexpr auto lambda = [](unsigned long) static {}; + using lambda_type = std::remove_const_t; + + STATIC_REQUIRE_FALSE( + polyfill::is_detected_v); + STATIC_REQUIRE( + polyfill::is_detected_v); + } +#endif + SECTION("freestanding function") { + constexpr auto quotedScalar = "f"_scalar.quote(std::clamp); + using quoted_type = decltype("f"_scalar.quote(std::clamp)); - STATIC_REQUIRE(std::is_same_v)*>); - STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(quotedScalar._nme[0] == 'f' && quotedScalar._nme[1] == '\0'); + STATIC_REQUIRE(std::is_same_v), + const int&(const int&, const int&, const int&), + 2>>); - STATIC_REQUIRE(std::is_same_v::return_type, int>); - STATIC_REQUIRE( - std::is_same_v::args_tuple, std::tuple>); + STATIC_REQUIRE(std::is_same_v)>); + STATIC_REQUIRE(std::is_same_v); - STATIC_REQUIRE(orm_quoted_scalar_function); - STATIC_REQUIRE_FALSE(orm_scalar_function); + STATIC_REQUIRE(std::is_same_v::return_type, int>); + STATIC_REQUIRE( + std::is_same_v::args_tuple, std::tuple>); - STATIC_REQUIRE(std::is_same_v>); + STATIC_REQUIRE(orm_quoted_scalar_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); - using storage_type = decltype(make_storage("")); - STATIC_REQUIRE(storage_scalar_callable); + STATIC_REQUIRE( + std::is_same_v>); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + } + SECTION("template function") { + constexpr auto quotedScalar = "f"_scalar.quote(std::clamp); + using quoted_type = decltype("f"_scalar.quote(std::clamp)); + + STATIC_REQUIRE(quotedScalar._nme[0] == 'f' && quotedScalar._nme[1] == '\0'); + STATIC_REQUIRE(std::is_same_v), + const int&(const int&, const int&, const int&), + 2>>); + + STATIC_REQUIRE(std::is_same_v)>); + STATIC_REQUIRE(std::is_same_v); + + STATIC_REQUIRE(std::is_same_v::return_type, int>); + STATIC_REQUIRE( + std::is_same_v::args_tuple, std::tuple>); + + STATIC_REQUIRE(orm_quoted_scalar_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); + + STATIC_REQUIRE( + std::is_same_v>); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + } + SECTION("lambda") { + constexpr auto lambda = [](unsigned long errcode) { + return errcode != 0; + }; + using lambda_type = std::remove_const_t; + constexpr auto quotedScalar = "f"_scalar.quote(lambda); + using quoted_type = std::remove_const_t; + + STATIC_REQUIRE(quotedScalar._nme[0] == 'f' && quotedScalar._nme[1] == '\0'); + STATIC_REQUIRE(std::is_same_v>); + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + + STATIC_REQUIRE(std::is_same_v::return_type, bool>); + STATIC_REQUIRE( + std::is_same_v::args_tuple, std::tuple>); + + STATIC_REQUIRE(orm_quoted_scalar_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); + + STATIC_REQUIRE( + std::is_same_v>); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + } +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + SECTION("static lambda") { + constexpr auto lambda = [](unsigned long errcode) static { + return errcode != 0; + }; + using lambda_type = std::remove_const_t; + constexpr auto quotedScalar = "f"_scalar.quote(lambda); + using quoted_type = std::remove_const_t; + + STATIC_REQUIRE(quotedScalar._nme[0] == 'f' && quotedScalar._nme[1] == '\0'); + STATIC_REQUIRE(std::is_same_v>); + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + + STATIC_REQUIRE(std::is_same_v::return_type, bool>); + STATIC_REQUIRE( + std::is_same_v::args_tuple, std::tuple>); + + STATIC_REQUIRE(orm_quoted_scalar_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); + + STATIC_REQUIRE( + std::is_same_v>); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + } +#endif + SECTION("classic function object") { + constexpr auto quotedScalar = "f"_scalar.quote(std::equal_to{}); + using quoted_type = decltype("f"_scalar.quote(std::equal_to{})); + + STATIC_REQUIRE(quotedScalar._nme[0] == 'f' && quotedScalar._nme[1] == '\0'); + STATIC_REQUIRE(std::is_same_v< + decltype(quotedScalar), + const quoted_scalar_function, bool(const int&, const int&) const, 2>>); + + STATIC_REQUIRE(std::is_same_v>); + STATIC_REQUIRE(std::is_same_v); + + STATIC_REQUIRE(std::is_same_v::return_type, bool>); + STATIC_REQUIRE(std::is_same_v::args_tuple, std::tuple>); + + STATIC_REQUIRE(orm_quoted_scalar_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); + + STATIC_REQUIRE(std::is_same_v>); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + } + SECTION("transparent function object") { + constexpr auto quotedScalar = "f"_scalar.quote(std::equal_to{}); + using quoted_type = decltype("f"_scalar.quote(std::equal_to{})); + + STATIC_REQUIRE(quotedScalar._nme[0] == 'f' && quotedScalar._nme[1] == '\0'); + STATIC_REQUIRE(std::is_same_v< + decltype(quotedScalar), + const quoted_scalar_function, bool(const int&, const int&) const, 2>>); + + STATIC_REQUIRE(std::is_same_v>); + STATIC_REQUIRE(std::is_same_v); + + STATIC_REQUIRE(std::is_same_v::return_type, bool>); + STATIC_REQUIRE(std::is_same_v::args_tuple, std::tuple>); + + STATIC_REQUIRE(orm_quoted_scalar_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); + + STATIC_REQUIRE(std::is_same_v>); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + } + SECTION("function object overloaded call operator") { + constexpr auto quotedScalar = "f"_scalar.quote(functor{}); + using quoted_type = decltype("f"_scalar.quote(functor{})); + + STATIC_REQUIRE(quotedScalar._nme[0] == 'f' && quotedScalar._nme[1] == '\0'); + STATIC_REQUIRE( + std::is_same_v>); + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + + STATIC_REQUIRE(std::is_same_v::return_type, bool>); + STATIC_REQUIRE(std::is_same_v::args_tuple, std::tuple>); + + STATIC_REQUIRE(orm_quoted_scalar_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); + + STATIC_REQUIRE(std::is_same_v>); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + } +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + SECTION("function object with static call operator") { + constexpr auto quotedScalar = "f"_scalar.quote(functor{}); + using quoted_type = decltype("f"_scalar.quote(functor{})); + + STATIC_REQUIRE(quotedScalar._nme[0] == 'f' && quotedScalar._nme[1] == '\0'); + STATIC_REQUIRE( + std::is_same_v>); + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + + STATIC_REQUIRE(std::is_same_v::return_type, bool>); + STATIC_REQUIRE(std::is_same_v::args_tuple, std::tuple>); + + STATIC_REQUIRE(orm_quoted_scalar_function); + STATIC_REQUIRE_FALSE(orm_scalar_function); + + STATIC_REQUIRE(std::is_same_v>); + + using storage_type = decltype(make_storage("")); + STATIC_REQUIRE(storage_scalar_callable); + } +#endif } #endif } diff --git a/tests/user_defined_functions.cpp b/tests/user_defined_functions.cpp index 8f7361ffa..006e79a62 100644 --- a/tests/user_defined_functions.cpp +++ b/tests/user_defined_functions.cpp @@ -217,6 +217,18 @@ struct alignas(2 * __STDCPP_DEFAULT_NEW_ALIGNMENT__) OverAlignedAggregateFunctio }; #endif +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED +struct StaticCallOpFunction { + static bool operator()(int x, int y) { + return x == y; + } + + static const char* name() { + return "STATICCALLOP"; + } +}; +#endif + struct NonAllocatableAggregateFunction { void step(double /*arg*/) {} @@ -483,6 +495,16 @@ TEST_CASE("custom functions") { } #endif +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + storage.create_scalar_function(); + { + auto rows = storage.select(func(1, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); +#endif + storage.create_scalar_function(42); { auto rows = storage.select(func(1)); @@ -526,6 +548,26 @@ struct stateful_scalar { inline constexpr stateful_scalar offset0{}; TEST_CASE("generalized scalar udf") { + struct functor { + bool operator()(int&, int&) const = delete; + + bool operator()(const int&, const int&) const { + return true; + } + +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + static bool operator()(int, int) { + return true; + } +#endif + }; +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + // note: this static lambda lives up here because of GCC 13.2 and mangling issues + constexpr auto lambda_static_dummy = [](unsigned long errcode) static { + return errcode != 0; + }; +#endif + auto storage = make_storage(""); storage.sync_schema(); @@ -551,6 +593,20 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + SECTION("stateless static lambda") { + constexpr auto is_fatal_error_f = "is_fatal_error"_scalar.quote([](unsigned long errcode) static { + return errcode != 0; + }); + storage.create_scalar_function(); + { + auto rows = storage.select(is_fatal_error_f(1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } +#endif SECTION("function object instance") { constexpr auto equal_to_int_f = "equal_to"_scalar.quote(std::equal_to{}); storage.create_scalar_function(); @@ -593,6 +649,18 @@ TEST_CASE("generalized scalar udf") { } storage.delete_scalar_function(); } +#ifdef SQLITE_ORM_STATIC_CALL_OPERATOR_SUPPORTED + SECTION("function object instance with static call operator") { + constexpr auto f = "f"_scalar.quote(functor{}); + storage.create_scalar_function(); + { + auto rows = storage.select(f(0, 1)); + decltype(rows) expected{true}; + REQUIRE(rows == expected); + } + storage.delete_scalar_function(); + } +#endif SECTION("specialized template function") { constexpr auto clamp_int_f = "clamp_int"_scalar.quote(std::clamp); storage.create_scalar_function();