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 tostd::false_type
. It has avalue
of typebool
. Likewisecbx::true_type
.cbx::type_switch_t
is equivalent toswitch
clause but, for types and evaluated statically at compile time.cbx::branch
is like theif
branch. The first argument is the condition and must have avalue
of typebool
. The second argument is the type itself.cbx::otherwise
is like theelse
branch.
Things I know
- It does not work for private constructors. (I am okay with that!)
- It's a rough implementation.
Things I want to know
- Is this implementation correct?
- Am I missing anything?
- How close it is to the actual
std::is_constructible
? - How can I improve?
C++ Standard: 20
1 Answer 1
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. :)
-
\$\begingroup\$ Did I do it write? godbolt.org/z/T1Kxrjbch @Quuxplusone \$\endgroup\$Mansoor Ahmed Memon– Mansoor Ahmed Memon2022年01月23日 09:08:53 +00:00Commented Jan 23, 2022 at 9:08
-
\$\begingroup\$ Are there any other edge/special/unhandled cases? @Quuxplusone \$\endgroup\$Mansoor Ahmed Memon– Mansoor Ahmed Memon2022年01月23日 09:11:16 +00:00Commented 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 onis_constructible<int&, float&>
. The expression you chose to SFINAE on,T(x)
, can do a hidden reinterpret_cast. \$\endgroup\$Quuxplusone– Quuxplusone2022年01月23日 19:02:54 +00:00Commented Jan 23, 2022 at 19:02
Explore related questions
See similar questions with these tags.