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

\$\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. :)

\$\endgroup\$
3

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.