2

In order to circumvent the restriction on partially supplied explicit template arguments, I embed the struct from which I want to deduce the class template parameters (Internal) into a second struct (Container).

I would like to enable the user of the code to create e.g. shared pointers of the resulting type. By writing my own create function within the struct, this works just fine.

#include <memory>

/// Container that is used in order to partially specify template arguments
template <int A> struct Container {

  /// Contained type, of which the template arguments are deduced.
  template <int B> struct Internal {
    explicit Internal(std::integral_constant<int, B> fu) { (void)fu; }
  };

  /// Helper function
  template <int C>
  [[nodiscard]] static auto create(std::integral_constant<int, C> t) noexcept {
    return std::make_shared<Container<A>::Internal<C>>(t);
  }
};


int main() {
  Container<1>::Internal works{std::integral_constant<int, 8>{}};
  auto const worksAswell = Container<1>::create(std::integral_constant<int, 8>{});
}

But when I try to use make_shared directly, it fails. I would like to enable the user to use e.g. the std::make_shared function.

int main() {
  auto const fails = std::make_shared<Container<1>::Internal>(std::integral_constant<int, 8>{}); 
}

From what I understand, this fails because I cannot partially specify template arguments, and I am unable to deduce them from the make_shared function if I don't want to specify all template parameters.


main.cc: In function ‘int main()’:
main.cc:21:74: error: no matching function for call to ‘make_shared<1>(std::integral_constant<int, 8>)’
   21 |   auto const fails = std::make_shared<1>(std::integral_constant<int, 8>{});
      |                                                                          ^
In file included from /usr/include/c++/9.2.0/memory:81,
                 from /home/juli/main9.cc:1:
/usr/include/c++/9.2.0/bits/shared_ptr.h:714:5: note: candidate: ‘template<class _Tp, class ... _Args> std::shared_ptr<_Tp> std::make_shared(_Args&& ...)’
  714 |     make_shared(_Args&&... __args)
      |     ^~~~~~~~~~~
/usr/include/c++/9.2.0/bits/shared_ptr.h:714:5: note:   template argument deduction/substitution failed:

Is it possible to enable generator functions like std::make_shared to partially deduce template arguments like that? The entire code can be found here.

mutableVoid
  • 1,284
  • 2
  • 11
  • 29
  • 1
    How general does this have to be? You could make your own `make_shared` and have it take a template template parameter. You would need to know the sagnature/signatures of the templates you are expecting to pass in though, if they are mixing type and non-type parameters. – super Jan 23 '20 at 20:04
  • I tried to create my own `make_shared` function, but I did not succeed, because I want to specify the template parameters only partially and do not know how to do so when operating from `std` namespace (i.e. not from within my `Container` struct). I would prefer a solution which does not repeat the constructor signature, but I am fine with repeating it if necessary. – mutableVoid Jan 23 '20 at 20:10
  • 1
    You misinterpret the problem. You're not specifying `make_shared` template parameters partially, you're specifying a *template id* (which is `Container<1>::Internal`) where a type is expected. You could add an overload of `make_shared` that takes a template template parameter as the first parameter and then forwards the call to the actual `make_shared` by composing the actual type to allocate from the template template parameter and function argument types. – Andrey Semashev Jan 23 '20 at 20:32

1 Answers1

2

If you create your own make_shared that accepts a template template parameter we can use decltype to deduce the resulting type and pass that on to std::make_shared.

#include <memory>
#include <type_traits>

/// Container that is used in order to partially specify template arguments
template <int A> struct Container {

  /// Contained type, of which the template arguments are deduced.
  template <int B> struct Internal {
    explicit Internal(std::integral_constant<int, B> fu) { (void)fu; }
  };
};

template <template <int> typename partial, typename... Args>
auto make_shared(Args&&... args) {
    using target_type = std::remove_pointer_t<decltype(new partial{std::declval<Args>()...})>;
    return std::make_shared<target_type>(std::forward<Args>(args)...);
}

using std::make_shared;

int main() {
  auto const fails = make_shared<Container<1>::Internal>(std::integral_constant<int, 8>{});
  static_assert(std::is_same_v<const std::shared_ptr<Container<1>::Internal<8>>, decltype(fails)>);
}

The only issue here is that our make_shared needs to know the template signature of the expected target.

On the positive side we can add several overloads for different template signatures and we can use one parameter pack.

super
  • 12,335
  • 2
  • 19
  • 29
  • Thanks very much for the solution :) I have got one remaining question though. In your type declaration, you use `remove_pointer_t` on the `decltype`. For me `using target_type = decltype(partial{std::declval()...});` also works. Is there something I am not considering when doing so, or is it fine aswell? – mutableVoid Jan 24 '20 at 13:56
  • If used without `remove_pointer_t` then `target_type = Container<1>::Internal<8>*` instead of `Container<1>::Internal<8>`. It still works because `std::shared_ptr` has a constructor that takes a pointer-type and does the right thing. – super Jan 24 '20 at 15:02
  • I'm sorry, I think I misphrased what I wanted to say, instead of using `make_shared(... new partial ...)` I use `decltype(partial...)` and want to make 100% sure that I am not inadvertently creating an instance my my change (which should not be the case from what I understand because I am using that in a `using type = ...` context which is evaluated at compile time from what I understand – mutableVoid Jan 24 '20 at 15:06
  • 1
    Oh, you mean like that. That's actually better, more direct. Guess I was just in the mindset of creating pointers so I took a little detour there. – super Jan 24 '20 at 15:09
  • 1
    And yes, anything inside `decltype(...)` in an un-evaluated context. It's only a look-up that happens at compile-time and no actual objects are being constructed. – super Jan 24 '20 at 15:10