1

In the program below, case 1 attempts to use a default parameter via pointer-to-member-function. Case 2 attempts to use a default parameter via function reference. Case 3 uses the default parameter in operator(). The only interesting assertions here are the ones using the alias can_call_with_one - the others exist to prove correctness of the setup.

In the latest versions of GCC, Clang, and MSVC that are available to me, this program fails the single-argument assertions in cases 1 and 2.

My question is twofold:

  1. Are these results consistent with the ISO C++ standard?
  2. If so, why does case 3 not fail?
#include <type_traits>
#include <utility>

struct substitution_failure {};

substitution_failure check(...);

template<typename Pmf, typename T, typename... Args>
auto check(Pmf pmf, T t, Args&&... args) ->
    decltype((t.*pmf)(std::forward<Args>(args)...))*;

template<typename Fn, typename... Args>
auto check(Fn&& f, Args&&... args) ->
    decltype(f(std::forward<Args>(args)...))*;

template<typename T>
using test_result = std::integral_constant<bool,
    !std::is_same<T, substitution_failure>::value
>;

template<typename... Ts>
auto can_invoke(Ts&&... ts) ->
    test_result<decltype(check(std::forward<Ts>(ts)...))>;

namespace case_1 {

    //pointer to member function

    struct foo {
        int bar(int, int = 0);
    };

    using can_call_with_one = decltype(can_invoke(&foo::bar, foo{}, 0));
    using can_call_with_two = decltype(can_invoke(&foo::bar, foo{}, 0, 0));
    using can_call_with_three = decltype(can_invoke(&foo::bar, foo{}, 0, 0, 0));

    static_assert(can_call_with_one{}, "case 1 - can't call with one argument");
    static_assert(can_call_with_two{}, "case 1 - can't call with twp arguments");
    static_assert(!can_call_with_three{}, "case 1 - can call with three arguments");
}

namespace case_2 {

    //function reference

    int foo(int, int = 0);

    using can_call_with_one = decltype(can_invoke(foo, 0));
    using can_call_with_two = decltype(can_invoke(foo, 0, 0));
    using can_call_with_three = decltype(can_invoke(foo, 0, 0, 0));

    static_assert(can_call_with_one{}, "case 2 - can't call with one argument");
    static_assert(can_call_with_two{}, "case 2 - can't call with two arguments");
    static_assert(!can_call_with_three{}, "case 2 - can call with three arguments");
}


namespace case_3 {

    //function object

    struct foo {
        int operator()(int, int = 0);
    };

    using can_call_with_one = decltype(can_invoke(foo{}, 0));
    using can_call_with_two = decltype(can_invoke(foo{}, 0, 0));
    using can_call_with_three = decltype(can_invoke(foo{}, 0, 0, 0));

    static_assert(can_call_with_one{}, "case 3 - can't call with one argument");
    static_assert(can_call_with_two{}, "case 3 - can't call with two arguments");
    static_assert(!can_call_with_three{}, "case 3 - can call with three arguments");
}

int main() { return 0; }

runnable version

Barrett Adair
  • 1,306
  • 10
  • 24
  • clearly, `pmf` is deduced as a member function taking two arguments, so it's not callable with a single argument, while for a function object you don't deduce the signature of `operator()`, you just check if an instance of `foo` is callable – Piotr Skotnicki Mar 13 '16 at 09:13
  • Sure, but my question is, _why_? The compiler knows as much about the PMF as it does the function reference and function object - at least, it _can_. I just now realized that the behavior is the same outside of the substitution context. I always though you could call a PMF and use the default argument, but you can't. This does make my question less interesting, but I would still like to know specifically why default arguments are ignored in some contexts and not in others, and what those contexts are, concretely. I probably just need to peruse the standard. If I find an answer, I'll post it. – Barrett Adair Mar 13 '16 at 14:01
  • I do realize that default arguments are not part of the signature - that's why I use the actual pointer value in this example rather than `std::declval`. Also, I got the same results when placing the pointer in an `std::integral_constant`. – Barrett Adair Mar 13 '16 at 14:03
  • This is interesting: http://stackoverflow.com/questions/35980469/does-the-c-standard-explicitly-disallow-default-arguments-in-calls-through-con – Jerry Jeremiah Mar 14 '16 at 19:22

1 Answers1

0

Type trait of the function does not come with the information of the default parameters. If it would you wouldn't be able to assign a pointer of a function with default value like:

void foo(int, int = 0) {...}

to:

void(*fp)(int, int);
fp = &foo;

The question is now if the language would allow for that - should the values that are default for a given parameters also identify the type of a function? This would imply that the default value of the parameter should be a constexpr, and as such would restrict the usability of the default values. This way for example parameters of type const char * couldn't have default value defined inline...

On the other hand if the type of the function would carry only the information that a given parameter has a default value without the knowledge of the value itself - compiler wouldn't be able to reconstruct the default value of the function from the pointer while the function invocation.

W.F.
  • 13,888
  • 2
  • 34
  • 81
  • Thanks for answering. I disagree with your conclusion that this functionality would require the default argument's initializer clause to be `constexpr` - the runtime evaluation of the expression does not have any bearing on the type of the function - the _type_ of the default argument's initializer clause expression is known and checked at compile time, `constexpr` or not. However, I do realize that the function type itself (`void(int,int)` in your example) does not encode any information about default arguments, and (continued)... – Barrett Adair Mar 14 '16 at 04:40
  • I do agree that it would not be possible for the language to be changed to accommodate this. `constexpr` could come into play with the actual pointer value - that's what I was getting at in my second comment reply to @PiotrSkotnicki above. Perhaps this is a better way to frame my original question: Is the compiler allowed to detect the presence of default parameters of a function referred to by a `constexpr` function pointer? Does that make sense? Edit: obviously, my example doesn't use `constexpr` function pointers, as they are function parameters. I'll update my example to reflect this. – Barrett Adair Mar 14 '16 at 04:45
  • I've been browsing the C++ working draft N4567, but I have not yet found an answer. I would specifically like for the accepted answer to refer to the standard. I'll update my original post to be more clear about this. – Barrett Adair Mar 14 '16 at 04:48
  • Actually, I'm going to go ahead and mark your answer as accepted, and open a new question, since I'm probably moving the target a bit. Thanks! – Barrett Adair Mar 14 '16 at 04:57