No I shouldn't. I use this frequently in c++ to deduplicate common functionality that only differs by a single line. The "flag" is a zero-size struct and checking the flag is done at compile time.
struct do_bar_tag {}
template<typename do_bar_t>
int foo(int x, do_bar_t) {
if constexpr (is_same<do_bar_t, do_bar_tag>::value) {
bar(x);
}
...
}
The compiler will emit two implementations, as you suggest is proper, but my maintenance overhead is reduced. And the cost is zero, in time and space, at runtime.Of course, this isn't always the right thing to do, and you can definitely overdo it. But in moderation, it's fine. "Code smell" is too often a needlessly strong opinion.
foo(int x, do_bar_t)
With time, it turns into foo(int x, int y, do_bar_t)
and eventually foo(int x, int y, int z, int w, do_bar_t)
It's too tempting to add 'just one more' flag. You de-duplicate common functionality but increase overall complexity.The valid complaint about the implementation I show would be that the do_bar logic is at the very top with no preamble, and it may be simpler for the caller to simply call bar(x) in that case. But adding necessary-looking complexity would detract from the clarity of the implementation (and damn c++'s verbosity, it already looks like hot garbage IMHO)
You might not "use ints as flags" but a lot of maintenance programmers who know nothing about your idea, have never seen the code before, don't know how the application works or even what it does, will do just that.
Moreover, if these unrestricted-novice-rogue-developer-maintainer-rhinoceri are going to do whatever the hell they feel like, completely disregarding the patterns and practices that I have established in my code, does it matter what I have done?
In languages with good support for functional features, you can curry and compose functions to your heart's content, allowing you to reuse similar logic and take better advantage of type systems like TypeScript's
But well, usually dynamicizing statically-known types yields premature abstraction
I use compile-time flags to deduplicate logic because refactoring is a nightmare if tens to hundreds of lines of code are turned into boilerplate. That is to say, I ensure conceptual integrity by maintaining a single source of truth. The nice thing about relatively dry code is that if you need to decouple it, you can easily just duplicate the code in question and run with that. If you have a dozen slightly-different copies of the same boilerplate, it's very rare in my experience for the variations to be well documented, so it ends up taking a lot of work to figure out why the variations exist -- and the real pitfall that I've seen time and time again is that a bug is found in one variant, fixed there, and not propagated to the other variants.
I love that you've called this out. So many people try and justify their coding preferences by referring to this mythical 'we' or the 'coding community' who have apparently all agreed to to some certain standard and everyone should just fall in line and get behind it without asking any questions. It's such a load of bullshit.
Fair point, but in my ~20 years of experience, I've learned what does and doesn't make for more work down the road. To wit: if something is always bad, I don't keep doing it. But I didn't just say I do it frequently, I said that I do it frequently for big-ish functions when it's only toggling a line or so. That context is important: I am not arguing that this is always a good thing. I'm saying that there's a time & place for it, and with a language like C++, it's a zero-cost abstraction.
> ...I don’t agree with the example as provided.
Yeah, neither do I. As I pointed out elsewhere, its a contrived example which can be more simply factored out into two function calls. So unless it's being used tens to hundreds of times, nothing is gained there.
Good, I'm glad. For some reason the term "Code smell" has itself become a "smell" to me.