0

I'm using C++17. I made following code that is supposed to use SFINAE to test if lambda is compilable or not (lambda is always syntactically correct but may be uncompilable e.g. due to absence of some methods used in body):

Try it online!

#include <type_traits>
#include <sstream>
#include <iostream>
#include <tuple>

template <typename ... Ts>
struct Types {
    using types = std::tuple<Ts...>;
    template <size_t I>
    using type = std::tuple_element_t<I, types>;
};

template <typename F, typename Enable = void, typename ... Ts>
struct Compilable : std::false_type {};

template <typename F, typename ... Ts>
struct Compilable<F,
        std::void_t<decltype((*(F*)0)(Types<Ts...>{}))>, Ts...>
    : std::true_type {};

template <typename ... Ts, typename F>
bool constexpr Comp(F const & f) {
    return Compilable<F, void, Ts...>::value;
}

template <typename T>
void Test() {
    std::cout << Comp<T>([](auto Ts){
        typename decltype(Ts)::template type<0> * p = 0;
        std::stringstream ss; ss << (*p); }) << std::endl;
}

struct X {};

int main() {
    Test<int>();
    Test<X>();
}

I hoped that uncompilable specialization will be silently excluded due to SFINAE, but instead it throws compilation error (for the second Test<X>() case, first test compiles):

<source>:30:34: error: invalid operands to binary expression 
   ('std::stringstream' (aka 'basic_stringstream<char>') and
   'typename decltype(Ts)::type<0>' (aka 'X'))
        std::stringstream ss; ss << (*p); }) << std::endl;

What am I doing wrong? Am I using SFINAE mechanism wrongly? What should be the right way to implement code above?

Arty
  • 14,883
  • 6
  • 36
  • 69
  • 1
    SFINAE works if you don't refer/call a failuring object. You call it. – 273K Jun 25 '21 at 16:12
  • @S.M. But I'm taking `decltype()` of call result, only deriving type of result, so actual call is never done. – Arty Jun 25 '21 at 16:13
  • @S.M. So can you suggest how to fix my code above to make it work? Maybe you can post fix as an answer? – Arty Jun 25 '21 at 16:13
  • @S.M. BTW I need solution for any lambda (maybe at least lambda without capture). My `std::stringstream` example is just one of examples, not only it should be solved. – Arty Jun 25 '21 at 16:20
  • Somehow like this https://stackoverflow.com/questions/6534041/how-to-check-whether-operator-exists for the operator<<. – 273K Jun 25 '21 at 16:21
  • @S.M. As I told in previous comment above, I don't need solution for `std::stringstream` specifically, I made just a toy example out of it. I need solution for any lambda (at least for lambda without capture). – Arty Jun 25 '21 at 16:24
  • operator is a function, that example can be adopted to any not capturing lambda. – 273K Jun 25 '21 at 16:26
  • @S.M. Following your suggestion for 1 hour I tried different things to adopt your linked example, but without any success. So if some time later you have time please post an answer solving my task if you don't mind. – Arty Jun 25 '21 at 17:02
  • 3
    There is no way to take something that sits in the *body* of a function or a function template and ask "does this thing compile?". The way SFINAE works, roughly speaking, is by trying to instantiate a bunch of templates (overloads or specialisations), and rejecting instantiations that do not produce a comipleable *header* (not body). You cannot try to instantiate a template with an argument that is not valid on its own. – n. m. could be an AI Jun 25 '21 at 17:20
  • @n.1.8e9-where's-my-sharem. Do you know at least if it is possible to solve my task without SFINAE somehow? I.e. if I have any (without capture) lambda, can I figure out somehow if it is compilable? Lambdas will be syntactically valid C++ code, but may be uncompilable due to absence of some methods or fields used in the body. Or other reasons of uncompilability may be too. I need generic solution for lambdas, of cause I can use SFINAE to check if method exists, but I need to validate any syntactically valid code. – Arty Jun 25 '21 at 17:40
  • This is possible with concepts. – n. m. could be an AI Jun 25 '21 at 18:03
  • @n.1.8e9-where's-my-sharem. I need C++17 solution. Concepts are probably C++20? – Arty Jun 25 '21 at 18:04
  • @n.1.8e9-where's-my-sharem. If you have time can you please post answer about how to solve my task using C++20 concepts? The task of checking compilability of any lambda. Currently I don't want to use some special `requires` syntax to rewrite code of lambda. I want to know if any complex (one screen) lambda can be checked for compilability even using C++20 concepts. – Arty Jun 25 '21 at 18:12
  • I don't think it is possible with C++17. – n. m. could be an AI Jun 25 '21 at 18:39
  • Now I don't think it is possible with concepts either. – n. m. could be an AI Jun 25 '21 at 18:46
  • 2
    There are dupes [here](https://stackoverflow.com/questions/63507674/is-lambdification-of-a-concept-an-improvement-or-bad-practice) and [here](https://stackoverflow.com/questions/64670361/c20-concept-requires-expression-to-test-if-generic-lambda-accepts-type). Long story short, no, you cannot SFINAE or conceptify *statements*, including those that are inside a lambda. – n. m. could be an AI Jun 25 '21 at 18:57
  • 2
    Only invalid types and expressions in the [immediate context](https://timsong-cpp.github.io/cppwp/n4659/temp.deduct#8) of the function type and its template parameter types can result in a deduction failure. – rustyx Jun 25 '21 at 20:23

1 Answers1

2

I think problem is that you try SFINAE based error in body of lambda. And if I understand this is far to late to recovery from error.

Base idea of SFINAE is to remove incompatible function from overload set, not to to recover from compilation failures, line is bit blurred, but probably best rule of thumb is that error need happen only in function declaration.

e.g.


template<typename T>
struct Get
{
    using type = typename T::type;
};

void F1(...){}
template<typename T, typename TT = typename Get<T>::type>
void F1(T i){}

void F2(...){}
template<typename T, typename TT = typename T::type>
void F2(T i){}


int main() {
    //F1(3); //hard error
    F2(3); //SFINAE - work fine
}

Type Get<T> fail to be created, in theory compiler could recover from this but this could be costly (image error happens in deep hierarchy). F2 fail correctly because compiler only need to look on function header to see if it is correct.

If you could move required check to template header then it could in theory work. Something like:


template <typename T>
void Test() {
    std::cout << Comp<T>(
        [](auto Ts, decltype((*(std::stringstream*)0) << (*(typename decltype(Ts)::template type<0>*)0))* = {})
        {
            typename decltype(Ts)::template type<0> * p = 0;
            std::stringstream ss; ss << (*p);
        }
    ) << std::endl;
}

Second lambda parameter is evaluated on call site and can "visibly" fails applying arguments. Its not give results as you want but it compile, probably you would need update pram type to correctly reflect operation in lambda.

Yankes
  • 1,958
  • 19
  • 20