Example
struct foo { int value{42}; };
struct bar { };
constexpr auto value = [](auto t) {
if constexpr ( requires { t.value; } ) {
return t.value;
} else {
return 0;
}
};
static_assert(42 == value(foo{}));
static_assert(0 == value(bar{}));
Puzzle
- Can you implement a bash style constexpr ternary operator powered by C++20 concepts?
/* TODO
* Bash style constexpr ternary operator powered by C++20 concepts
* [[ $b = 5 ]] && a="$c" || a="$d"
*/
int main() {
struct foo { int value{42}; };
struct bar { };
{
constexpr auto value = [](auto t) { return requires { t.value; }; } && [](auto t) { return t.value; } || [](auto t) { return 0; };
static_assert(0 == value(bar{}));
static_assert(42 == value(foo{42}));
}
{
constexpr auto value = [](auto t) { return requires { t.value; }; } and [](auto t) { return t.value; } or [](auto t) { return sizeof(t); };
static_assert(sizeof(bar) == value(bar{}));
static_assert(42 == value(foo{42}));
}
{
constexpr auto value = [](auto t) { return requires { t.value; }; } and [](auto t) { return t.value; } or 0;
static_assert(0 == value(bar{}));
static_assert(42 == value(foo{42}));
}
{
constexpr auto value = [](auto t) { return not requires { t.value; }; } and 1 or [](auto t) { return t.value; };
static_assert(1 == value(bar{}));
static_assert(42 == value(foo{42}));
}
{
constexpr auto value = [](auto t) { return requires { t.value; }; } and 1 or 2;
static_assert(2 == value(bar{}));
static_assert(1 == value(foo{}));
}
{
constexpr auto value = [](auto t) { return requires { t.value; }; } and 1;
static_assert(0 == value(bar{}));
static_assert(1 == value(foo{}));
}
{
constexpr auto value = [](auto t) { return requires { t.value; }; } or 1;
static_assert(1 == value(bar{}));
static_assert(0 == value(foo{}));
}
{
static_assert(4 == ([](auto t) { return requires { t.value; }; } and 4 or 2)(foo{}));
static_assert(2 == ([](auto t) { return requires { t.value; }; } and 4 or 2)(bar{}));
}
}
Solutions
namespace detail {
constexpr auto call_or_value = [] (auto f, [[maybe_unused]] auto t) {
if constexpr (requires { f(t); }) {
return f(t);
} else {
return f;
}
};
template <class Cond, class Cons, class Alt>
struct alt {
template <class T>
constexpr auto operator()(T t) const {
if constexpr (Cond{}(T{})) {
return call_or_value(c, t);
} else {
return call_or_value(a, t);
}
}
Cons c;
Alt a;
};
template <class Cond, class Cons>
struct cond {
template <class T>
constexpr auto operator()(T t) const {
if constexpr (Cond{}(T{})) {
return call_or_value(c, t);
} else {
return decltype(call_or_value(c, t)){};
}
}
Cons c{};
};
}
template <class Cond, class Cons>
constexpr auto operator and(Cond, Cons c) {
return detail::cond<Cond, Cons>{c};
}
template <class Cond, class Cons, class Alt>
constexpr auto operator or(detail::cond<Cond, Cons> cond, Alt a) {
return detail::alt<Cond, Cons, Alt>{cond.c, a};
}
template <class Cond, class Alt>
constexpr auto operator or(Cond, Alt a) {
constexpr auto neg = [] <class T> (T t) {
return not Cond{}(t);
};
return detail::cond<decltype(neg), Alt>{a};
}
namespace detail {
template <typename T, bool B>
struct test
{
private:
T value;
public:
constexpr test() : value{} {}
constexpr test(T t) : value(t) {}
template <typename = std::enable_if<not std::same_as<bool, T>>>
constexpr operator bool() const { return B; }
constexpr operator T() const { return value; }
};
template <typename>
struct has_value : std::false_type {};
template <typename T, bool B>
struct has_value<test<T, B>> { static constexpr bool value = B; };
template <typename T>
constexpr bool has_value_v = has_value<T>::value;
constexpr auto result(auto expr, auto val)
{
if constexpr (requires { expr(val); }) return expr(val);
else return expr;
}
} // namespace detail
template <typename Pred, typename Expr>
constexpr auto operator and(Pred, Expr expr)
{
return [=]<typename T>(T value)
{
if constexpr (Pred{}(T{})) {
auto result = detail::result(expr, value);
return detail::test<decltype(result), true>(result);
}
else {
return detail::test<Expr, false>{};
}
};
}
template <typename Pred, typename Expr>
constexpr auto operator or(Pred pred, Expr expr)
{
return [=](auto value)
{
auto cond = pred(value);
if constexpr (std::same_as<decltype(cond), bool>) {
if (cond) {
return detail::test<Expr, true>(Expr{});
}
}
if constexpr (not detail::has_value_v<decltype(cond)>) {
auto result = detail::result(expr, value);
return detail::test<decltype(result), true>(result);
}
else {
return cond;
}
};
}
namespace detail {
[[nodiscard]] constexpr auto call(auto f, auto v) {
if constexpr (requires { f(v); }) {
return f(v);
} else {
return f;
}
}
template<class TLhs, class TRhs>
struct if_ {
constexpr if_(TLhs lhs, TRhs rhs) : lhs{lhs}, rhs{rhs} {}
[[nodiscard]] constexpr auto operator()(auto x, auto y) const {
if constexpr (TLhs{}(decltype(x){})) {
return call(rhs, x);
} else {
return call(y, x);
}
}
[[nodiscard]] constexpr auto operator()(auto t) const {
if constexpr (TLhs{}(decltype(t){})) {
return call(rhs, t);
} else {
return decltype(call(rhs, t)){};
}
}
private:
TLhs lhs{};
TRhs rhs{};
};
template<class TLhs, class TRhs>
struct else_ {
constexpr else_(TLhs lhs, TRhs rhs) : lhs{lhs}, rhs{rhs} {}
[[nodiscard]] constexpr auto operator()(auto t) const {
if constexpr (requires { lhs(t, rhs); }) {
return lhs(t, rhs);
} else if constexpr (TLhs{}(decltype(t){})) {
return decltype(call(rhs, lhs)){};
} else {
return call(rhs, lhs);
}
}
private:
TLhs lhs{};
TRhs rhs{};
};
} // namespace detail
[[nodiscard]] constexpr auto operator and(auto lhs, auto rhs) { return detail::if_{lhs, rhs}; }
[[nodiscard]] constexpr auto operator or(auto lhs, auto rhs) { return detail::else_{lhs, rhs}; }
template <typename FuncOrVal, typename Val>
constexpr auto maybe_call(FuncOrVal funcorval, Val val) {
if constexpr (requires{funcorval(Val{});})
return funcorval(val);
else
return funcorval;
}
template <typename Condition, typename IfTrue>
struct and_t {
Condition condition{};
IfTrue iftrue{};
template <typename Value>
constexpr auto operator()(Value value) const {
if constexpr (Condition{}(Value{})) {
return maybe_call(iftrue, value);
} else {
return decltype(maybe_call(iftrue, value)){};
}
}
};
template <typename Condition, typename IfTrue>
constexpr auto operator&&(Condition condition, IfTrue iftrue) {
return and_t{condition, iftrue};
}
template <typename Condition, typename IfTrue, typename IfFalse>
constexpr auto operator||(and_t<Condition, IfTrue> and_, IfFalse iffalse) {
return [=]<typename T>(T value) {
if constexpr (and_.condition(T{})) {
return maybe_call(and_.iftrue, value);
} else {
return maybe_call(iffalse, value);
}
};
}
template <typename Condition, typename IfFalse>
constexpr auto operator||(Condition condition, IfFalse iffalse) {
return [=]<typename T>(T value) {
if constexpr (not condition(T{})) {
return maybe_call(iffalse, value);
} else {
return decltype(maybe_call(iffalse, value)){};
}
};
}
template<typename Cond, typename TRes, typename FRes = int>
struct Ternary {
constexpr Ternary(Cond cond, TRes tres, FRes fres = 0) : cond(cond), tres(tres), fres(fres) {}
template<typename T>
auto constexpr operator()(const T& t) const {
TRes tresLocal = tres;
FRes fresLocal = fres;
Cond condLocal = cond;
if constexpr( condLocal(T())) {
if constexpr( std::is_fundamental_v<TRes>)
return tresLocal;
else
return tresLocal(t);
} else {
if constexpr( std::is_fundamental_v<FRes>)
return fresLocal;
else
return fresLocal(t);
}
}
Cond cond;
TRes tres;
FRes fres;
};
template<typename Cond, typename TRes, typename FRes>
auto constexpr operator||(Ternary<Cond, TRes> ternary, FRes fres) {
return Ternary(ternary.cond, ternary.tres, fres);
}
template<typename Cond, typename FRes>
auto constexpr operator||(Cond cond, FRes fres) {
return Ternary(cond, 0, fres);
}
template<typename Cond, typename TRes>
auto constexpr operator&&(Cond cond, TRes tres) {
return Ternary(cond, tres);
}
template <typename TCompare, typename TTrue, typename TFalse = int>
struct compare {
constexpr explicit(true) compare(const TCompare&, const TTrue& true_in) : true_val(true_in), false_val(0) {}
constexpr explicit(true) compare(const compare<TCompare, TTrue, int>& partial, const TFalse& false_in)
: true_val(partial.true_val)
, false_val(false_in)
{
}
template <typename TValue>
constexpr auto operator()(const TValue& value) const
{
if constexpr (TCompare{}(TValue{})) {
if constexpr (requires(TValue v) { TTrue{}(v); }) {
return TTrue{}(value);
} else {
return true_val;
}
} else {
if constexpr (requires(TValue v) { TFalse{}(v); }) {
return TFalse{}(value);
} else {
return false_val;
}
}
}
TTrue true_val;
TFalse false_val;
using compare_sentenel = void;
};
constexpr auto operator and(const auto& compare_func, const auto& true_in)
{
return compare{compare_func, true_in};
}
template<typename TCompare>
constexpr auto operator or(const TCompare& lhs, const auto& false_in)
{
if constexpr (requires{TCompare::compare_sentenel;}) {
return compare{lhs, false_in};
} else {
return compare{compare{lhs, 0}, false_in};
}
}