Skip to content

[CXX-2625] More type traits + invoke() #1042

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Nov 7, 2023

Conversation

vector-of-bool
Copy link
Contributor

@vector-of-bool vector-of-bool commented Oct 19, 2023

(CXX-2625) This changeset adds many additional type traits from C++14 and beyond, especially for easier "SFINAE trickery". Many are C++14/17 traits, some are from library fundamentals, and some are neither.

This changeset does not fully replace any existing dependencies, but will make it easier to do so going forward, as well as cleaning up some of our own metaprogramming trickery.

@eramongodb
Copy link
Contributor

eramongodb commented Oct 23, 2023

Some type traits in this PR uses alias templates, e.g.:

template <typename Type, typename... Traits>
using requires_t = enable_if_t<conjunction<Traits...>::value, Type>;

Additionally, some constraints are expressed as return types (the traditional std::enable_if pattern):

template <typename T>
bsoncxx::stdx::requires_t<void, bsoncxx::stdx::is_alike<T, int>> //
only_int(T) {} // Example A

Instead, I suggest (1) going all-in on defining type traits using named structs whenever possible (including the implementation of _t variants) instead of alias templates, as is already being done for some type traits (e.g. bsoncxx::stdx::conjunction):

template <typename Type, typename... Traits>
struct requires : std::enable_if<conjunction<Traits...>::value, Type> {};

template <typename Type, typename... Traits>
using requires_t = typename requires<Type, Traits...>::type;

And (2) moving the constraints into the template parameter list instead of the return type (as is already being done for some ctors, e.g. bsoncxx::document::value::value()):

template <typename T, bsoncxx::stdx::requires_t<void, bsoncxx::stdx::is_alike<T, int>>* = nullptr>
void only_int(T) {} // Example B

Both suggestions are to improve the quality of error messages. The currently proposed changes emit the following error message on constraint failure for the only_int example (note: no meaningful information about the failed constraint is visible in the message):

error: no matching function for call to 'only_int'
    only_int(0.0);
    ^~~~~~~~
note: candidate template ignored: substitution failure [with T = double]: no type named 'type' in 'std::enable_if<false>'
only_int(T) {} // Example A
^

Suggestion 1 ensures the named type trait is embedded in the error message, rather than its obscure substituted implementation details (suggestion applied to both requires and is_alike for this example):

error: no matching function for call to 'only_int'
    only_int(0.0);
    ^~~~~~~~
note: candidate template ignored: substitution failure [with T = double]: no type named 'type' in 'bsoncxx::stdx::requires<void, bsoncxx::stdx::is_alike<double, int>>'
only_int(T) {} // Example A
^

Suggestion 2 avoids the noisy mess that obscures the return type in the function declaration (+ the noisy repetition of the constraint when the return type happens to be on the same line as the function name), while still preserving clean SFINAE semantics that is consistent across all functions (including ctors), and also being friendlier to ClangFormat even for complex constraints (no //-injection workarounds required):

error: no matching function for call to 'only_int'
    only_int(0.0);
    ^~~~~~~~
note: candidate template ignored: substitution failure [with T = double]: no type named 'type' in 'bsoncxx::stdx::requires<void, bsoncxx::stdx::is_alike<double, int>>'
void only_int(T) {} // Example B
     ^

Additionally, a suggestion 3: despite the convenience of requires_t, I am partial to using typename std::enable_if<cond..., type>::type despite its verbosity due to the quality of error messages by compilers that special-case substition failure messages for std::enable_if. e.g. given (suggestions 1 and 2 are applied here; note the clean ClangFormat results):

template <typename T,
          bsoncxx::stdx::requires_t<void,
                                    bsoncxx::stdx::is_alike<T, double>,
                                    bsoncxx::stdx::is_alike<T, int>>* = nullptr>
void impossible(T) {}

The error message looks as follows (the entire constraint is emitted as-is):

error: no matching function for call to 'impossible'
    impossible(0.0);
    ^~~~~~~~~~
note: candidate template ignored: substitution failure [with T = double]: no type named 'type' in 'bsoncxx::stdx::requires<void, bsoncxx::stdx::is_alike<double, double>, bsoncxx::stdx::is_alike<double, int>>'
void impossible(T) {}
     ^

Whereas if using explicit std::enable_if:

template <typename T,
          typename std::enable_if<bsoncxx::stdx::is_alike<T, double>::value &&
                                  bsoncxx::stdx::is_alike<T, int>::value>::type* = nullptr>
void impossible(T) {}

The error message looks as follows (more meaningful and specific reason for constraint failure):

error: no matching function for call to 'impossible'
    impossible(0.0);
    ^~~~~~~~~~
note: candidate template ignored: requirement 'bsoncxx::stdx::is_alike<double, int>::value' was not satisfied [with T = double]
void impossible(T) {}
     ^

@@ -22,6 +22,7 @@ else()
bsoncxx_add_library(bsoncxx_testing "bsoncxx-testing" STATIC)
endif()

file(GLOB src_bsoncxx_test_DIST_cpps CONFIGURE_DEPENDS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove unused src_bsoncxx_test_DIST_cpps?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet addressed.

@vector-of-bool
Copy link
Contributor Author

@eramongodb The avoidance of instantiating class templates is intentional since it explodes can explode compilation time. I was also unsatisfied with the opaque diagnostic messages on constraint failure, so I've added some changes that will produce better error messages on almost all compilers, and often better than the compilers' special-cases for enable_if.

Most oddities in the placement of the SFINAE constraints are specifically to appease VS2015, which has horribly broken SFINAE behavior. When we drop VS2015 support, we will be safe to relocate the constraints to the template parameter lists in (almost?) all contexts (without needing to add the typename=void disambiguators).

@@ -22,6 +22,7 @@ else()
bsoncxx_add_library(bsoncxx_testing "bsoncxx-testing" STATIC)
endif()

file(GLOB src_bsoncxx_test_DIST_cpps CONFIGURE_DEPENDS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet addressed.

@eramongodb eramongodb removed their request for review November 6, 2023 15:49
@eramongodb eramongodb self-requested a review November 6, 2023 15:49
Copy link
Contributor

@eramongodb eramongodb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it difficult to review what traits are implemented in such a manner to avoid VS 2015 compatibility issues vs. what are not, but the intent/context is understood.

@@ -84,8 +86,7 @@ class BSONCXX_API value {
/// @param t
/// A user-defined object to serialize into a BSON object.
///
template <typename T,
typename std::enable_if<!std::is_same<T, typename array::view>::value, int>::type = 0>
template <typename T, _traits::requires_not_t<int, std::is_same<T, array::view>> = 0>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concern: unlike all other functions in this PR that use SFINAE, this ctor is not declared with BSONCXX_INLINE or BSONCXX_PRIVATE despite being a member of a BSONCXX_API-declared class, meaning instantiations are part of the public ABI. Although this does not yet pose an issue (given it's still a member of the unstable ABI namespace), this might be something we want to address before/during its migration into the stable ABI namespace.

Side-note: we may want to establish a style/coding guideline for the CXX Driver libraries that ABI-stability of template instantiations are not supported even for members of the stable ABI namespace (or classes in that namespace) to avoid the complexity of maintaining ABI-stability of symbols that depend on our traits/SFINAE utilities.


namespace bsoncxx {

BSONCXX_INLINE_NAMESPACE_BEGIN
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
BSONCXX_INLINE_NAMESPACE_BEGIN
inline namespace v_noabi {

Consistency with changes in #1039.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could that macro be removed completely, or is it too much part of the public API?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was considered, but I think it's been part of the API for too long to risk its sudden removal. I intend to document them as deprecated as part of CXX-2745 work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants