8

In my C++ class we are learning to use function objects and the like, but now we got a code snippet that works on teacher's compiler but not on ours (we use different OS's).

We tested the code snippet below with several compilers (MSVC, clang) and they all reject it, a bit minimized:

#include <functional>

struct Fraction {
  Fraction();
  Fraction(int z, int n);
  Fraction(Fraction&);

  // various data members
};

struct FractionComparator {
  int operator()(Fraction a, Fraction b) {
    return 1;
  }
};

int main() {
  std::function<int(Fraction, Fraction)> comparator = FractionComparator();
}

We get on clang on macOS:

No viable conversion from 'FractionComparator' to 'function<int (Fraction, Fraction)>'

We already found out that adding a move constructor solves the problem, but we have no idea why this difference exists and why this code doesn't compile on our compilers.

Any ideas?

Hong Ooi
  • 56,353
  • 13
  • 134
  • 187

2 Answers2

13

Why does adding a move-constructor solve the problem?

Fraction is attempted to be copy-constructed from an rvalue.

But constuctor Fraction(Fraction&); takes a non-constant reference. Non-const references are not allowed to bind to temporaries. The proper constructor signature should be:

Fraction(const Fraction&);

When you declare a move constructor compiler will move-construct Fraction from an rvalue instead.

Why doesn't this code compile on our compilers, but compiles on the teacher's?

This code compiles with VC++. It looks like the compiler is not conforming to the standard here. I could find this StackOverflow question with some more detail. It seems to be a compiler extension that allows this to compile. If compiler extensions are disabled via /Za flag it will not compile anymore.

muru
  • 4,723
  • 1
  • 34
  • 78
AMA
  • 4,114
  • 18
  • 32
  • 3
    [error reproduction](https://wandbox.org/permlink/QYC5AWVrpod3hM4c), [proof of fix](https://wandbox.org/permlink/E0GiICZOgUEK01cP). – Marek R Jun 04 '18 at 18:03
  • 1
    _Typical Microsoft's EEE in action..._ or typical Microsoft focus on [backwards](http://ptgmedia.pearsoncmg.com/images/9780321440303/samplechapter/Chen_bonus_ch01.pdf) [compatibility](https://blogs.msdn.microsoft.com/e7/2009/03/03/application-compatibility-testing-overview/) – David Brown Jun 04 '18 at 20:05
  • 1
    @DavidBrown Since this code was never legal in standard C++, I'd ask backwards compatibility with what? Cfront 2.0 maybe? Or, more realistically, just an older bugged MSVC… That said, GCC is good at EE, too. Anyone remember the obscure type erasure with `signature` pointers? – Arne Vogel Jun 04 '18 at 21:30
  • @ArneVogel backwards compatibility with earlier versions of MSVC that had the same extension. They don't want their support staff to deal with thousands of complaints if the code stops working when the compiler updates – M.M Jun 04 '18 at 22:56
  • @M.M Ok, I'll give you that MSVC has supported C++ before it was standardized, and the behavior may date back to that time. However, a cleaner solution would be not to enable this extension at the same time as newer language features. As in, depending on option settings, either you compile in backwards compatibility mode or you get the new and cleaner stuff. Anyway, that's my opinion… – Arne Vogel Jun 05 '18 at 09:11
7

Starting from C++14 the constructor you are trying to use does not participate in overload resolution unless the target function is callable with the given set of arguments (with std::declval<>() arguments of given types). In your case that would be std::declval<Fraction>() arguments.

Your FractionComparator functor is not callable with std::declval<Fraction>() arguments since it receives its arguments by value, while temporary objects of type Fraction cannot be copied: Fraction's copy-constructor's argument is declared as a non-const reference.

Declare your copy constructor as

Fraction(const Fraction &);

and the code will compile. If you have a good reason to keep it as non-const, then you will have to explore other opportunities for making this work. Why does your FractionComparator insist on receiving its parameters by value anyway?

Your question is tagged C++11, but apparently your compiler is retroactively implementing this C++14 requirement even in C++11 mode.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • The C++14 change was that the constructor is SFINAEd away. C++11 still required the passed callable [to be callable with specified arguments](https://timsong-cpp.github.io/cppwp/n3337/func.wrap.func.con#7). – Rakete1111 Jun 04 '18 at 17:32