Say I'm using an external package for storing graphs. A BidirectionalGraph takes two templates: a vertex and an edge type:
var graph = new BidirectionalGraph<Vertex, Edge<Vertex>>();
Unfortunately, this graph package doesn't allow you to get the edges radiating into a vertex in a single line. Instead, you have to provide an IEnumerable
, which it will populate with the results. This can disrupt a good coding rhythm by making tasks like "loop through all vertices that are successors of vertex x
" take far too much code.
I wanted to use .NET's extensions to add a one-line solution to the graph class:
public static class GraphExtensions
{
public static IEnumerable<TEdge> IncomingEdges<TGraphSubtype, TVertex, TEdge>(this TGraphSubtype graph, TVertex n)
where TGraphSubtype : BidirectionalGraph<TVertex, TEdge>
where TEdge : IEdge<TVertex>
{
IEnumerable<TEdge> inputEdgesForVertex;
graph.TryGetInEdges(n, out inputEdgesForVertex);
return inputEdgesForVertex;
}
}
But when I call graph.IncomingEdges(vertex)
, for some reason C# (.NET version 4.5) can't infer the template arguments, so I have to say:
graph.IncomingEdges<GraphThatInheritsFromBidirectionalGraph<VertexType,EdgeType>,VertexType,EdgeType>(vertex)
. Not really a great improvement.
First, why can't the template types be estimated? I have a feeling it has to do with inheritance, but don't understand. I'm used to using C++, and for some reason feel that gcc could infer the template types.
Second, if this can't be prevented, is the correct design choice to make a graph class for actual use, which inherits from BidirectionalGraph? It seems a waste to have to rewrite the constructors, but I'm sure you'd agree that calling the method with explicit template types is inelegant.
EDIT:
Strangely, the equivalent specification (below) does allow automatic inference of template types. So, even though it solves my initial problem (adding this functionality to the graph), I'd still really like to understand.
public static class GraphExtensions
{
public static IEnumerable<TEdge> IncomingEdges<TVertex, TEdge>(this BidirectionalGraph<TVertex,TEdge> graph, TVertex n)
where TEdge : IEdge<TVertex>
{
IEnumerable<TEdge> inputEdgesForVertex;
graph.TryGetInEdges(n, out inputEdgesForVertex);
return inputEdgesForVertex;
}
}
Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function's return type, from the return statement.
A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)
Which parameter is legal for non-type template? Explanation: The following are legal for non-type template parameters:integral or enumeration type, Pointer to object or pointer to function, Reference to object or reference to function, Pointer to member.
A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.
The first version of your extension method is able to infer TGraphType
and TVertex
but not TEgde
, as it would require inferring the TEdge
from the type constraint:
where TGraphSubtype : BidirectionalGraph<TVertex, TEdge>
which C# compiler does not do (it does not infer generic type parameters from type constraints). I honestly don't know if there is a technical reason behind this or it just wasn't implemented.
Your updated version, on the other hand, includes BidirectionalGraph<TVertex, TEdge>
as a parameter, so for example when you call the extension method on a class like:
class AGraph: BidirectionalGraph<AVertex, AnEdge> { ... }
...
var aGraph = new AGraph();
aGraph.IncomingEdges(vertex);
the compiler is able to examine the type AGraph
and see that there is a unique type BidirectionalGraph<AVertex, AnEdge>
in its inheritance hierarchy, so it is able to infer TVertex
and TEdge
.
Note that if the parameter type were IGraph<TVertex, TEdge>
(instead of BidirectionalGraph<TVertex, TEdge>
) and AGraph
implemented multiple constructed types of that generic interface, e.g.:
class AGraph: IGraph<AVertex, AnEdge>,
IGraph<AnotherVertex, AnotherEdge> { ... }
then type inference would fail once again because it can't tell if, for example, TVertex
is AVertex
or AnotherVertex
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With