Make a function accepting an optional to accept a non-optional?
Make a function accepting an optional to accept a non-optional?
I'm trying to write syntactic sugar, in a monad-style, over std::optional
. Please consider:
std::optional
template<class T>
void f(std::optional<T>)
As is, this function cannot be called with a non-optional T
1 (e.g. an int
), even though there exists a conversion from T
to std::optional<T>
2.
T
int
T
std::optional<T>
Is there a way to make f
accept an std::optional<T>
or a T
(converted to an optional at the caller site), without defining an overload3?
f
std::optional<T>
T
1)f(0)
: error: no matching function for call to 'f(int)'
and note: template argument deduction/substitution failed
, (demo).
2) Because template argument deduction doesn't consider conversions.
3) Overloading is an acceptable solution for a unary function, but starts to be an annoyance when you have binary functions like operator+(optional, optional)
, and is a pain for ternary, 4-ary, etc. functions.
f(0)
error: no matching function for call to 'f(int)'
note: template argument deduction/substitution failed
operator+(optional, optional)
@xaxxon that's worth an answer
– YSC
Aug 21 at 9:01
Can you clarify what you mean by “in a monad-style”? Because as it stands your question suggests you are interested in some kind of automatic conversion or overload, and both answers do something along these lines. But this has nothing whatsoever to do with monads.
– Konrad Rudolph
Aug 21 at 10:43
@KonradRudolph The mention of monads was just to give a context. I want, provided
F=λxy.
, an optional(x)
and an optional(y)
, return an optional(F(xy))
. For such a definition to be useful and let its user chain expressions, it needs to accept both optionals and scalars.– YSC
Aug 21 at 11:39
F=λxy.
optional(x)
optional(y)
optional(F(xy))
The problem with this definition is that, unlike a monad, it does not compose. How would your function handle a
std::optional<std::optional<T>>
? Whereas if you had a monad lifting operator you would simply lift(F)
and wouldn’t have this problem.– Konrad Rudolph
Aug 21 at 12:49
std::optional<std::optional<T>>
lift(F)
4 Answers
4
Another version. This one doesn't involve anything:
template <typename T>
void f(T&& t)
std::optional opt = std::forward<T>(t);
Class template argument deduction already does the right thing here. If t
is an optional
, the copy deduction candidate will be preferred and we get the same type back. Otherwise, we wrap it.
t
optional
Even though this is not code golf, I've got the feeling this answer deserve the checkmark :D
– YSC
Aug 21 at 13:15
Good line of thinking; however, doesn't opt need to be defined as std::optional<T> here?
– LThode
Aug 21 at 15:34
@LThode Very much no.
– Barry
Aug 21 at 15:37
@Barry -- a-ha, I see what you mean now -- I've never bumped into class template argument deduction before!
– LThode
Aug 21 at 15:40
This lacks overload resolution features; is there a way to test if
std::optional opt = std::forward<T>(t)
would compile?– Yakk - Adam Nevraumont
Aug 21 at 17:42
std::optional opt = std::forward<T>(t)
Instead of taking optional as argument take deductible template parameter:
template<class T>
struct is_optional : std::false_type;
template<class T>
struct is_optional<std::optional<T>> : std::true_type;
template<class T, class = std::enable_if_t<is_optional<std::decay_t<T>>::value>>
constexpr decltype(auto) to_optional(T &&val)
return std::forward<T>(val);
template<class T, class = std::enable_if_t<!is_optional<std::decay_t<T>>::value>>
constexpr std::optional<std::decay_t<T>> to_optional(T &&val)
return std::forward<T>(val) ;
template<class T>
void f(T &&t)
auto opt = to_optional(std::forward<T>(t));
int main()
f(1);
f(std::optional<int>(1));
Live example
This uses one of my favorite type traits, which can check any all-type template against a type to see if it's the template for it.
#include <iostream>
#include <type_traits>
#include <optional>
template<template<class...> class tmpl, typename T>
struct x_is_template_for : public std::false_type ;
template<template<class...> class tmpl, class... Args>
struct x_is_template_for<tmpl, tmpl<Args...>> : public std::true_type ;
template<template<class...> class tmpl, typename... Ts>
using is_template_for = std::conjunction<x_is_template_for<tmpl, std::decay_t<Ts>>...>;
template<template<class...> class tmpl, typename... Ts>
constexpr bool is_template_for_v = is_template_for<tmpl, Ts...>::value;
template <typename T>
void f(T && t)
auto optional_t = [&]
if constexpr (is_template_for_v<std::optional, T>)
return t;
else
return std::optional<std::remove_reference_t<T>>(std::forward<T>(t));
();
(void)optional_t;
int main()
int i = 5;
std::optional<int> oi5;
f(i);
f(oi);
https://godbolt.org/z/HXgoEE
Really nice trait you've got here. Do you know why
std::decay
is necessary?– YSC
Aug 21 at 9:22
std::decay
@YSC Without
std::decay
it would not work automagically with types like std::optional<int> &
and related ones– bartop
Aug 21 at 9:31
std::decay
std::optional<int> &
Another version. This one doesn't involve writing traits:
template <typename T>
struct make_optional_t
template <typename U>
auto operator()(U&& u) const
return std::optional<T>(std::forward<U>(u));
;
template <typename T>
struct make_optional_t<std::optional<T>>
template <typename U>
auto operator()(U&& u) const
return std::forward<U>(u);
;
template <typename T>
inline make_optional_t<std::decay_t<T>> make_optional;
template <typename T>
void f(T&& t)
auto opt = make_optional<T>(std::forward<T>(t));
@YSC Shrug. Wrote it a different way instead.
– Barry
Aug 21 at 15:05
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
have it take a T and then just check if it's an optional and if it's not, make it one. constexpr if can do that super easy if you're using c++17.
– xaxxon
Aug 21 at 9:00