0

The following does not compile:

#include <iostream>
#include <type_traits>

template <class F, class G>
auto static_if (std::true_type, F && f, G && g) {
    return std::forward<F> (f) (0);
}   

template <class F, class G>
auto static_if (std::false_type, F && f, G && g) {
    return std::forward<G> (g) (0);
}


template <class T> std::true_type constexpr is_pointer (T *) { return {}; }
template <class T> std::false_type constexpr is_pointer (T) { return {}; }

int main() {
    int x = 5;
    static_if (
        is_pointer (x),
        [&] (auto) { std::cout << *x << std::endl; },
        [&] (auto) { std::cout << "no pointer" << std::endl; }
    );
    return 0;
}

Compilation fails with the error message:

prog.cpp: In lambda function:
prog.cpp:23:33: error: invalid type argument of unary '*' (have 'int')
      [&] (auto) { std::cout << *x << std::endl; },

See here: http://ideone.com/frl8rn

Now the question is: Why?

It is clear that compilation should fail if the lambdas were not generic (no auto parameter).

Yet we have two generic lambdas here. Each of them should only be instantiated when it is used (which means: called, or, at least, when its operator () is somehow referenced).

The generic lambda generating the error message should therefore not be instantiated, since the second overload of static_if is used, in the definition of which the parameter f is not called. Since the generic lambda's operator () is not used, the compiler should simply ignore it.

Where is my reasoning wrong?

JohnB
  • 12,033
  • 4
  • 34
  • 63
  • C++17 may help with `if constexpr`. – Yakk - Adam Nevraumont Jul 20 '16 at 04:52
  • Hm, now I read this: http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0128r1.html Wouldn't the exact same problem arise with `constexpr if`, without the possibility of solving it by delaying the type check, as, e.g., described in my comment to Jarod42's answer below? – JohnB Jul 20 '16 at 08:26

3 Answers3

4

In the comment to the accepted answer, you ask if there's a better solution. Arguably below is one (live code here). Instead of capturing x locally, you generalise your static_if to generically forward any arguments to either of the two lambdas, and make your x such an argument. Then you don't need the dance with dummy variables you have in the comment.

template <class F, class G, class ... Args>
auto static_if (std::true_type, F && f, G && g, Args && ... args) {
    return std::forward<F> (f) (std::forward<Args>(args)...);
}   

template <class F, class G, class ... Args>
auto static_if (std::false_type, F && f, G && g, Args && ... args) {
    return std::forward<G> (g) (std::forward<Args>(args)...);
}


template <class T> std::true_type constexpr is_pointer (T *) { return {}; }
template <class T> std::false_type constexpr is_pointer (T) { return {}; }

int main() {
    int x = 5;
    static_if (
        is_pointer (x),
        [] (auto x) { std::cout << *x << std::endl; },
        [] (auto x) { std::cout << "no pointer" << std::endl; },
        x
    );
    return 0;
}
Smeeheey
  • 9,162
  • 16
  • 37
  • I sort of like grouping the 3 kinds of parameters somehow (the condition, the lambda branches, and the arguments to thr lambdas). Also, the above requires an awkward wrapping if we want to use a `constexpr bool` and not `std::true_type` branching. – Yakk - Adam Nevraumont Jul 20 '16 at 04:55
3

x is a non template dependent type, so the check is done in first pass of template checking, as static_assert(false, "") fires even for non instantiated template.

Jarod42
  • 173,454
  • 13
  • 146
  • 250
  • Hence writing `[&] (auto dummy) { std::cout << *(dummy, x) << std::endl; }` solves the problem. See here: http://ideone.com/1yiLYo Thank you very much for your most helpful response. Is there a better solution than mine? – JohnB Jul 19 '16 at 10:43
1

[&x](auto){ std::cout<<*x<<'\n'; } is a lambda with a template operator(). Template operator()s that have no possible type passed to them that make them compile are not legal in C++.

The validity of *x does not change based on the template argument.

A good way around this is to change how your static_if works.

template <class F, class G>
auto static_branch (std::true_type, F && f, G && g) {
  return std::forward<F>(f);
}
template <class F, class G>
auto static_branch (std::false_type, F && f, G && g) {
  return std::forward<G>(g);
}
template <bool test, class F, class G>
auto static_branch(F && f, G && g) {
  return static_branch( std::integral_constant<bool, test>{}, std::forward<F>(f), std::forward<G>(g) );
}   

notice we don't invoke the f or g. Instead we return one or the other.

int main() {
  int x = 5;
  auto branch = static_branch (
    is_pointer (x),
    [&] (auto&&x) { std::cout << *x << std::endl; },
    [&] (auto&&) { std::cout << "no pointer" << std::endl; }
  );
  branch(x);
  return 0;
}

now the x is passed into the appropriate lambda. Which means only when x is a pointer is the *x code evaluated.

  int* px = &x;
  static_branch (
    is_pointer(y),
    [&] (auto&&x) { std::cout << *x << std::endl; },
    [&] (auto&&) { std::cout << "no pointer" << std::endl; }
  )(y);

would then invoke the first branch.

You could also wait for C++17, where constexpr if may solve your problem; I am uncertain.

Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465