What Comes to Mind

Stuff, and more stuff

Substitution is Sometimes a Failure
2025-11-28 12:06

Why are optional::transform and optional::and_then not constrained by invocable?

1. Optional "Monadic" Interface

Monadic operations for std::optional

  • transform is the c++ spelling for map or fmap
  • and_then is monadic bind for optional
  • or_else is dual to and_then

or_else also has:

Constraints: F models invocable and T models {move,copy}_constructible.

transform and and_then do not.

They don't work if you don't give them invocables, and rely on invoke_result_t to compute the result. So why not constrain them?

2. SFINAE is shallow

The problem is if another template has to be instantiated in order to evaluate the template of interest, and that template has an error, you get an error, not a substitution failure. And, it turns out, lambdas are a common source of the problem and a common case in real use.

Consider the code:

void f(int&);

auto l = [](auto& y) {
    f(y);
    return 42;
};

The two important parts are the auto& parameter and the implicit deduced return type.

We can rewrite it, to make things possibly more obvious:

struct Func {
    template <typename T>
    auto operator()(T& t) {
        f(t);
        return 42;
    }
};

and to slightly spoil things, the problem code is effectively:

static_assert(std::invocable<Func, int const&>);

which produces:

<source>: In instantiation of 'auto Func::operator()(T&) [with T = const int]':
type_traits:2565:26:   required by substitution of 'template<class _Fn, class ... _Args> static std::__result_of_success<decltype (declval<_Fn>()((declval<_Args>)()...)), std::__invoke_other> std::__result_of_other_impl::_S_test(int) [with _Fn = Func; _Args = {const int&}]'
type_traits:2576:55:   required from 'struct std::__result_of_impl<false, false, Func, const int&>'
type_traits:3038:12:   recursively required by substitution of 'template<class _Result, class _Ret> struct std::__is_invocable_impl<_Result, _Ret, true, std::__void_t<typename _CTp::type> > [with _Result = std::__invoke_result<Func, const int&>; _Ret = void]'
type_traits:3038:12:   required from 'struct std::is_invocable<Func, const int&>'
type_traits:3286:71:   required from 'constexpr const bool std::is_invocable_v<Func, const int&>'
concepts:336:25:   required from here
<source>:12:10: error: binding reference of type 'int&' to 'const int' discards qualifiers
   12 |         f(t);
      |         ~^~~
<source>:2:12: note:   initializing argument 1 of 'void f(int&)'
    2 |     void f(int&);
      |            ^~~~
Compiler returned: 1

Compiler Explorer

as the compiler is unhappy about trying to call the function f with a const int&.

If the lambda or Func is changed to have a non-deduced return type, the instantiation errors from the check to invocable go away, although you still get an error calling either with a const int.

So why do we run into this with transform if we were to constrain it with invocable ?

The compiler needs to figure out the overload set in order to resolve which one to use from the set. There are four of them two for the l- and r- value category and two for the const overloads.

template<class F> constexpr auto transform(F&& f) &;
template<class F> constexpr auto transform(F&& f) const &;
template<class F> constexpr auto transform(F&& f) &&;
template<class F> constexpr auto transform(F&& f) const &&;

with differing computations of the resulting optional being returned.

using U = invoke_result_t<F, decltype(std​::​move(*val))>;
//or
using U = remove_cv_t<invoke_result_t<F, decltype(*val)>>;

So, in order to work out what the templated transform's signature really is, it has to compute what the invocable returns, and since the invocable has deduced return type, it needs to instantiate it, and instantiating with const int causes an error.

This is unfortunate.

If we constrain transform we get the same errors as above. See here, with just enough of an optional to compile.

3. Constraints, what are they good for

Not absolutely nothing.

Constraints in the library:

Constraints: the conditions for the function's participation in overload resolution ([over.match]).

[Note 1: Failure to meet such a condition results in the function's silent non-viability. — end note]

[Example 1: An implementation can express such a condition via a constraint-expression ([temp.constr.decl]). — end example]

[structure.specifications] 3.1

Constraints are for making an overload not exist if the constraint isn't met. It's not a way of signaling an error. Those are Mandates:

Mandates: the conditions that, if not met, render the program ill-formed.

[Example 2: An implementation can express such a condition via the constant-expression in a static_assert-declaration ([dcl.pre]). If the diagnostic is to be emitted only after the function has been selected by overload resolution, an implementation can express such a condition via a constraint-expression ([temp.constr.decl]) and also define the function as deleted. — end example]

[structure.specifications] 3.2

Asking to run and_then on a non-invocable probably ought not to say there is no such function, but instead tell you it can't be invoked. I'm now not convinced that or_else should be constrained this way. It's not significantly better for o.or_else(5) to fail to resolve, mentioning invocable, than produce an error that invoke_result_t doesn't work, or that f can't be invoked. The kind of error is a minor detail.

Constraints that let you control the choice of alternatives are wonderful, and requires clauses are normal programmer accessible, unlike SFINAE, or even enable_if. But without an overload set to constrain, there possibly should not be a constraint.

4. Can we do better?

There are some notes in P0798 that suggest that Deducing This might help, P0847.

The idea would to be to NOT have all the value category overloads that need to be checked, but to just have a single one that deduces what this is and provide it as a template parameter for further use. The contained parameter could be forwarded using forward_like<Self>.

P0847 has discussion about how deducing this might be applied to optional. There's also discussion of deducing this and the SFINAE-unfriendly auto at C++23’s Deducing this: what it is, why it is, how to use it.

With the tools we have today, it looks possible, but still slightly messy. I managed to get my implementation of optional to compile and pass its own tests with.

template <class F, class Self>
    requires(
        std::invocable<F,
                       decltype(std::forward_like<Self>(std::declval<T>()))>)
constexpr auto transform(this Self&& self, F&& f)
    -> optional<std::invoke_result_t<
        F,
        decltype(std::forward_like<Self>(std::declval<T>()))>> {
    using U = std::invoke_result_t<F,
                                   decltype(std::forward_like<Self>(
                                       std::declval<T>()))>;
    static_assert(!std::is_array_v<U>);
    static_assert(!std::is_same_v<U, in_place_t>);
    static_assert(!std::is_same_v<U, nullopt_t>);
    static_assert(std::is_object_v<U> || std::is_reference_v<U>);
    if (self.has_value()) {
        return optional<U>{detail::from_function,
                           std::forward<F>(f),
                           std::forward_like<Self>(self.value_)};
    }
    return optional<U>;
}

If there were a std::forward_like_t, it might be possible to reduce some of the noise in computing the value category used for the T. I also have not thought extensively about if the requires clause is truly needed in light of the invoke_result_ that can now be used in the trailing return type.

Entry Fountain Pens
2025-01-19 11:07

A list of very good inexpensive fountain pens.

Read more…

Nikola Blog Infrastructure
2024-12-23 13:15

I've migrated from WordPress to a static blog generator–Nikola. This is how it works.

Read more…

Concept Maps using C++23 Library Tech
2024-05-19 11:51

Abstract

C++0x Concepts had a feature Concept Maps that allowed a set of functions, types, and template definitions to be associated with a concept and the map to be specialized for types that meet the concept.

This allowed open extension of a concept.

Read more…

Slides from C++Now 2023 Async Control Flow
2024-05-18 18:27

Using Sender/Receiver for Async Control Flow

Steve Downey

These are the slides, slightly rerendered, from my presentation at C++Now 2023.

Abstract

How can P2300 Senders be composed using sender adapters and sender factories to provide arbitrary program control flow?

  • How do I use these things?
  • Where can I steal from?

Read more…

Some Informal Remarks Towards a New Theory of Trait Customization
2023-12-24 00:00

A Possible Technique

constexpr bool g(int lhs, int rhs) {
    auto& op = partial_eq<int>;
    return op.ne(lhs, rhs);
}
Compiler Explorer with Supporting Code A trait is defined as a template variable that implements the required operations. Implementation of those operations is possible via a variety of techniques, but existence is concept checkable. It might prove useful to explicitly opt in to a sufficiently generic trait. The technique satisfies the openness requirement, that the trait can be created independently of the type that models the trait. There can still only be one definition, but this enables opting std:: types into new traits, for example. It also doesn't universally grab an operation name. The trait variable is namespaceable. Syntax isn't really awesome, but not utterly unworkable.

Read more…

Special Blocks and Emacs Org-mode Export
2021-12-03 00:00

  • Document number:
  • Date: 2021-12-03
  • Author: Steve Downey <sdowney2@bloomberg.net>, <sdowney@gmail.com>
  • Audience: WG21 Emacs Authors

Abstract: Making emacs org-mode more usable for writing WG21 papers.

Read more…

A local CMake workflow with Docker
2021-10-03 00:00

l#+BLOG: sdowney

An outline of a template that provides an automated workflow driving a CMake project in a docker container.

This post must be read in concert with https://github.com/steve-downey/scratch of which it is part.

Routine process should be automated

Building a project that uses cmake runs through a predictable lifecycle that you should be able to pick up where you left off without remembering, and for which you should be able to state your goal, not the step you are on. make is designed for this, and can drive the processs.

Read more…

std::execution, Sender/Receiver, and the Continuation Monad
2021-10-03 00:00

Some thoughts on the std::execution proposal and my understanding of the underlying theory.

What's proposed

From the paper's Introduction

This paper proposes a self-contained design for a Standard C++ framework for managing asynchronous execution on generic execution contexts. It is based on the ideas in [P0443R14] and its companion papers.

Which doesn't tell you much.

It proposes a framework where the principle abstractions are Senders, Receivers, and Schedulers.

Sender
A composable unit of work.
Receiver
Delimits work, handling completion, exceptions, or cancellation.
Schedulers
Arranges for the context work is done in.

Read more…

Standard Vocabulary for Algorithms
2021-01-16 18:22

This is feedback after considering A Plan for C++23 Ranges Disclosure: I voted in favor of this. It does not suggest work on views::maybe [P1255R6]​. I'm fine with that priority.

Vocabulary is not just Types between Components

There's broad agreement that 'vocabulary types' belong in the standard. Domain independent types that can be used between otherwise uncoupled facilities. They each depend on facilities provided by Standard C++ and can thus be developed independently. Each component can rely on the API of a core vocabulary type not changing. This allows facilities to communicate effectively. A component can have std::string or std::vector<int> in its interface with little concern. The STL parts of the standard library have always relied on concepts for constraining template parameters implicitly, requiring an Iterator of particular category in places, or specializing algorithms based on such. Composition of container types has also been natural. No one is confused by a std::unordered_map<std::string, std::vector<my::person>>.

Read more…