2
\$\begingroup\$

As a challenge/fun activity/task, I have implemented my version of std::is_constructible.

Here is the source code:

#include <cbrainx/cbrainx.hpp>
#include <iostream>
template <typename T, typename ...V>
auto aux(long) -> cbx::false_type;
template <typename T, typename ...V>
static auto aux(int) -> cbx::type_switch_t<
 cbx::branch<cbx::false_type,
 cbx::detail::void_t<decltype(::new(cbx::declval<void *>()) T{cbx::declval<V>()...})>>,
 cbx::otherwise<cbx::true_type>>;
template <typename T, typename ...V>
auto is_constructible() -> decltype(aux<T, V...>(0));
struct X {
 X() {
 std::cout << "constructed..." << std::endl;
 }
 private:
 X(int) {
 }
 public:
 X(const X &) = delete;
 X(X &&) {}
 ~X() {
 std::cout << "destroyed..." << std::endl;
 }
 static void xd() {
 X{1};
 std::cout << decltype(is_constructible<X, int>())::value << std::endl;
 }
};
auto main() -> int {
 std::cout << std::boolalpha;
 std::cout << decltype(is_constructible<X, X>())::value << std::endl;
 std::cout << decltype(is_constructible<X, X &>())::value << std::endl;
 std::cout << decltype(is_constructible<X, X &&>())::value << std::endl;
 return 0;
}

IMPORTANT:

  • cbx::false_type is equivalent to std::false_type. It has a value of type bool. Likewise cbx::true_type.

  • cbx::type_switch_t is equivalent to switch clause but, for types and evaluated statically at compile time.

  • cbx::branch is like the if branch. The first argument is the condition and must have a value of type bool. The second argument is the type itself.

  • cbx::otherwise is like the else branch.

Things I know

  1. It does not work for private constructors. (I am okay with that!)
  2. It's a rough implementation.

Things I want to know

  1. Is this implementation correct?
  2. Am I missing anything?
  3. How close it is to the actual std::is_constructible?
  4. How can I improve?

C++ Standard: 20

Incomputable
9,7043 gold badges34 silver badges73 bronze badges
asked Jan 21, 2022 at 15:00
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

It would help to cleanly separate your is_constructible from the "test harness" code that follows it — either with a //----- comment, or by putting your thing in a namespace, or something.

Your thing is a function template, whereas the standard std::is_constructible is a type-trait (that is, a class template).

Without seeing the definitions of branch, otherwise, etc., it's hard to judge whether your code is even correct, let alone performant.

You name both your template parameter T and your parameter pack V... with singular names. It would be helpful to name the pack with a plural name, e.g. Vs... or Args....

Your unit tests take the form of std::cout << foo. Prefer to use static_assert(foo), so that the compiler will tell you whether your tests passed or failed — you shouldn't have to eyeball the terminal output to tell whether you implemented is_constructible right!


You should become familiar with the traditional SFINAE idioms. For a simple type-trait like is_constructible, where all you want to know is whether a particular expression is well-formed, the idiom is

template<class T, class = void>
struct is_fooable : std::false_type {};
template<class T>
struct is_fooable<T, decltype((
 your-expression-here
), void())> : std::true_type {};

Or, since you tagged this question c++20, you could just use a requires-expression:

template<class T>
struct is_fooable : std::bool_constant<requires {
 your-expression-here;
}> {};

In the specific case of is_constructible, the expression you care about is new T(args...). So you might write

template<class, class T, class... Args>
struct is_constructible_impl : std::false_type {};
template<class T, class... Args>
struct is_constructible_impl<decltype((
 new T(std::declval<Args>()...)
), void()), T, Args...> : std::true_type {};
template<class T, class... Args>
struct is_constructible : is_constructible_impl<void, T, Args...> {};

Or:

template<class T, class... Args>
struct is_constructible : std::bool_constant<requires (Args&&... args) {
 new T(static_cast<Args&&>(args)...);
}> {};

(Godbolt.)


By writing unit tests and comparing them to the standard std::is_constructible, we soon find a problem: std::is_constructible<int&, int&>::value is true, but our new T(args...) formulation yields false, because you can't new a reference type. Solving this problem is left as an exercise for the reader, because I'm too lazy to look it up right now. :)

answered Jan 23, 2022 at 4:30
\$\endgroup\$
3
  • \$\begingroup\$ Did I do it write? godbolt.org/z/T1Kxrjbch @Quuxplusone \$\endgroup\$ Commented Jan 23, 2022 at 9:08
  • \$\begingroup\$ Are there any other edge/special/unhandled cases? @Quuxplusone \$\endgroup\$ Commented Jan 23, 2022 at 9:11
  • \$\begingroup\$ @MansoorAhmedMemon: Only one way to find out! Write test cases and compare your answers against std::is_constructible's answers. Try tricky things. godbolt.org/z/T1Kxrjbch seems to mess up on is_constructible<int&, float&>. The expression you chose to SFINAE on, T(x), can do a hidden reinterpret_cast. \$\endgroup\$ Commented Jan 23, 2022 at 19:02

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.