3

I'm trying to use template argument deduction in the 'perpendicular()' function:

#include <iostream>

template <typename component = double>
struct offset {
  component x;
  component y;
};

template <typename component>
offset(component x, component y) -> offset<component>;

template <typename component>
offset<component> perpendicular(offset<component> const &o) {
  return offset{o.y, -o.x};
}

template <typename component>
std::ostream &operator<<(std::ostream &s, offset<component> const &o) {
  return s << '(' << o.x << ", " << o.y << ')';
}

int main() {
  std::cout << perpendicular({3.1, 1.2}) << '\n';
  return 0;
}

This however doesn't compile; Clang (with -std='c++17') says: candidate template ignored: couldn't infer template argument 'component' offset<component> perpendicular(offset<component> const &o) {.

Should I resign to writing perpendicular(offset{1.0, 2.0}) or is there a way to give the compiler a hint?

Max Langhof
  • 23,383
  • 5
  • 39
  • 72

3 Answers3

3

Issue with {/*..*/} is that it has no type, and can mostly only be deduced as std::initializer_list<T> or T[N].

So following would allow desired syntax:

template <typename component>
offset<component> perpendicular(component const (&o)[2]) {
    return offset{o[1], -o[0]};
    // return perpendicular(offset{o[0], o[1]});
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • My understanding was the `{ ... }` is used for the initialization of an `offset`, which is in turn passed to `perpendicular` i.e. `offset x = {3.0, 2.1}; perpendicular(x)` (which works). I have also found out adding a default to the template parameter (`template `) makes it compile. – user11589013 Sep 26 '19 at 16:31
  • With `template offset perpendicular(offset)`, you would also got `double` for `perpendicular({42, 51})` (whereas `int` is expected) as deduction doesn't happen. – Jarod42 Sep 26 '19 at 16:42
  • Am I correct that the compiler looks for a `perpendicular(initializer_list)`, not a `perpendicular(offset{initializer_list})` when I call `perpendicular({3.12, 1.34})`? I'm still not 100% sure why this happens when there are no other overloads. – user11589013 Sep 26 '19 at 17:47
  • 2
    In fact, it is mostly done the other way: name lookup found `template perpendicular(offset)`, and you don't provide `offset`, so no deduction of `T`. (it doesn't try `offset{args}`). – Jarod42 Sep 26 '19 at 18:16
0

Jarod42's answer gives you the syntax you want, but I subjectively think it's not ideal. You originally wanted to pass in an offset, but now you are passing an array and turning it into an offset. It's a weird relationship of types.

Instead of a struct and separate functions, just put it all into an Offset class. It's not really any extra work, and makes for better C++. What you have is more akin to object-oriented C.

#include <iostream>

// Create a self-contained class
template <typename Component = double>
class Offset {
 public:
  Offset(Component x, Component y) : x(x), y(y) {}

  // No longer requires function parameters
  Offset const perpendicular() const { return Offset(y, -x); }

  // I appreciate your use of east const
  friend std::ostream& operator<<(std::ostream& sout,
                                  Offset<Component> const& o) {
    return sout << '(' << o.x << ", " << o.y << ')';
  }

 private:
  Component x;
  Component y;
};

int main() {
  // Subjectively much cleaner to read and understand
  std::cout << Offset{3.1, 1.2}.perpendicular() << '\n';
  return 0;
}

For future reference, can use decltype(auto) as your return type and forego the trailing return type syntax altogether as of C++14.

sweenish
  • 4,793
  • 3
  • 12
  • 23
  • `decltype(auto)` may be dangerous as return type since you may accidentally return a reference. In most cases where you want return type deduction, it should be just `auto`. – Eugene Sep 26 '19 at 15:25
  • 1
    `template offset(component x, component y) -> offset;` isn't a function, it is a deduction guide. – user11589013 Sep 26 '19 at 15:28
  • @Eugene `auto` w/ trailing return type syntax. `auto` alone isn't enough AFAIK. `decltype(auto)` will also only return a reference to a type if that's what you return, which would usually mean that's exactly what you intended. Putting the return type up to the compiler will always be a little bit risky, but type deduction pretty much works the way we want it to 99.9% of the time. Whether it helps my case or not, my recommendation is echoed by Scott Meyers in **Effective Modern C++**. – sweenish Sep 26 '19 at 15:32
  • @user11589013 A wholly unnecessary one if you use a class instead, but thank you for the clarification. – sweenish Sep 26 '19 at 15:34
  • I agree that my solution is not ideal, but I don't see benefit in `Offset{3.1, 1.2}.perpendicular()` contrary to OP: `perpendicular(Offset{3.1, 1.2})`. and I tend to prefer free function variant. – Jarod42 Sep 26 '19 at 16:38
  • I did say it was subjectively better. I also stated that I thought OP's current solution of `perpendicular(Offset{3.1, 1.2})` is better than changing the perpendicular function. I also prefer encapsulation where it makes sense, and I think it makes sense here. You don't really have perpendicular unless you have/want and Offset, so just bundle it together. The whole point of SO is for OP to get these different viewpoints and make a decision. And hopefully learn something new. – sweenish Sep 26 '19 at 16:42
  • You might read [how-non-member-functions-improve-encapsulation](https://embeddedartistry.com/blog/2017/2/27/how-non-member-functions-improve-encapsulation) (not the original link, but it seems down :-/ ). – Jarod42 Sep 26 '19 at 16:51
  • From the link: "adding functions beyond those truly necessary may be justifiable if it significantly improves the performance of the class, makes the class easier to use, or prevents likely client errors." I would then argue perpendicular() as a class member function can satisfy points 2 and 3. But again, it's still subjective. – sweenish Sep 26 '19 at 16:57
  • Similarly, I see the `<<` and `>>`overloads as non-member over friends as subjective. Maybe the original could have shed some light that might make me reconsider, but as friends they make the functions much easier to write. So I see a minor contradiction there. – sweenish Sep 26 '19 at 17:01
  • Both `auto` and `decltype(auto)` are allowed without trailing return type - starting from C++14. `auto` is safer as it never deduces a reference. `decltype(auto)` is needed in the rare cases when you need to preserve reference in the return type. You misunderstood "Effective Modern C++" (I read the book, too). – Eugene Sep 26 '19 at 18:17
  • I did not misunderstand it, `decltype(auto)` is literally used every single time the author shows a C++14 example function. And very nearly every single time, it's returning a value. Because, like I said, you have to go out of your way to return a reference. What I will give you is that I did not realize that just `auto` works as well. – sweenish Sep 26 '19 at 18:23
  • @sweenish: The case where `decltype(auto)` breaks is where you return an expression, other than a single unparenthesized name, that refers to an object whose lifetime does not extend outside the function call. – Davis Herring Sep 27 '19 at 02:19
  • And that's easy enough to recognize in your code. Like any other practice, examination and inspection is required. It doesn't go against anything I've said so far. – sweenish Sep 27 '19 at 13:44
0

One option is to add an overload to perpendicular that takes two values.

template <typename component>
offset<component> perpendicular(component v1, component v2) {
    return {v2, -v1};
}

This could also be made more generic with parameter packs, possibly combined with std::common_type.

super
  • 12,335
  • 2
  • 19
  • 29