Idea: Conditional code based on whether a type implements a trait

The problem I faced

I sometimes want to log values inside a generic function, but can't, because the type does not have std::fmt::Debug as a trait constraint. This means that I have to either add a trait constraint or give up.

Proposal

I wish there was a mechanism that allow me to use std::fmt::Debug without having to add it as a trait constraint. For example:

fn do_something<X>(x: &mut X) {
  transform1(x);
  if X impl std::fmt::Debug {
    eprintln!("after transform1: {:?}", x);
  }
  transform2(x);
  if X impl std::fmt::Debug {
    eprintln!("after transform2: {:?}", x);
  }
}

P.S. This is just an idea open for discussion. I don't intend on writing an RFC myself.

This would be covered by specialization, no?

1 Like

Do you have link to "specialization"? Anyway, before reading it, I guess that it would be more complicated than an if statement.

I suggest you check out this thread from a few days ago:

I think you are forgetting something important: complexity has to live somewhere.

Typically “simple and obvious” features are, in reality, harder to implement than features which look, superficially, more complex.

What you are asking for is, essentially, if constexpr from C++.

And if constexpr is, of course, harder to properly design and implement than specialization.

In fact if constexpr is built on top of machinery designed for template specialization support. And it was only added in C++17 in spite of the fact that template specialization was in C++98 already (although it took years to iron out bugs in the implementation).

Note that if constexpr is, actually, a minor complication over the specialization, the main complexity lies in the specialization machinery. But I don't think anyone would seriously consider it till specialization issues would be ironed out one way or another.

It may not be obvious to you just why your simple idea is quite complicated. But consider something like this:

fn do_something<X>(x: &mut X) {
  let mut log;
  if X impl std::fmt::Debug {
    log = fmt!("{:?}", x);
  }
  transform(x);
  if X impl std::fmt::Debug {
    eprintln!("transformed {} to {:?}", log, x);
  }
}

Is this valid code or not? What about this:

fn do_something<X>(x: &mut X) {
  let mut log;
  if X impl std::fmt::Debug {
    log = fmt!("{:?}", x);
  } else {
    log = 42;
  }
  transform(x);
  if X impl std::fmt::Debug {
    eprintln!("transformed {} to {:?}", log, x);
  }
}

What would happen if you are dozen of checks? To understand if your code is valid or not without exploring all the possibilities (which would lead to exponential compilation times because of combinatorial explosion) you would need some quite complicated machinery.

Thankfully it's pretty similar to what specialization already have to do and would, probably, be able to reuse some of the same machinery, but… such feature is, most definitely, harder to properly define and implement than specialization.

But sure, it's much easier to use. if constexpr made metaprogramming much more popular in C++ world. Because it become easier to implement simple things. But there are still a lot of issues related to it in the major C++ compilers.

5 Likes